[Zope-Checkins] SVN: Zope/branches/ajung-docutils-integration/ moving third_party/docutils back to lib/python

Andreas Jung andreas at andreas-jung.com
Sat Jan 15 10:47:16 EST 2005


Log message for revision 28841:
  moving third_party/docutils back to lib/python
  

Changed:
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/BUGS.txt
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/COPYING.txt
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/FAQ.txt
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/HISTORY.txt
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/PKG-INFO
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/README.txt
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/THANKS.txt
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/core.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/examples.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/frontend.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/io.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/af.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/cs.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/de.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/en.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/eo.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/es.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/fi.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/fr.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/it.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/pt_br.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/ru.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/sk.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/sv.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/zh_tw.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/nodes.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/admonitions.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/body.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/html.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/images.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/misc.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/parts.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/references.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/tables.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/af.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/cs.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/de.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/en.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/eo.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/es.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/fi.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/fr.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/it.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/pt_br.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/ru.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/sk.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/sv.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/zh_tw.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/roles.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/roman.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/states.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/tableparser.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/pep.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/moduleparser.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/pynodes.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/standalone.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/statemachine.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/components.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/frontmatter.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/misc.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/parts.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/peps.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/references.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/universal.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/urischemes.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/utils.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/__init__.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/docutils_xml.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/html4css1.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/latex2e.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/pep_html.py
  A   Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/pseudoxml.py
  D   Zope/branches/ajung-docutils-integration/lib/python/sitecustomize.py
  D   Zope/branches/ajung-docutils-integration/lib/python/third_party/
  U   Zope/branches/ajung-docutils-integration/setup.py

-=-
Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/BUGS.txt
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/BUGS.txt	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/BUGS.txt	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,254 @@
+================
+ Docutils_ Bugs
+================
+
+:Author: David Goodger; open to all Docutils developers
+:Contact: goodger at python.org
+:Date: $Date: 2005/01/07 15:11:43 $
+:Revision: $Revision: 1.1.2.1 $
+:Copyright: This document has been placed in the public domain.
+
+.. _Docutils: http://docutils.sourceforge.net/
+
+
+Bugs in Docutils?!?  Yes, we do have a few.  Some are old-timers that
+tend to stay in the shadows and don't bother anybody.  Once in a while
+new bugs are born.  From time to time some bugs (new and old) crawl
+out into the light and must be dealt with.  Icky.
+
+This document describes how to report a bug, and lists known bugs.
+
+.. contents::
+
+
+How To Report A Bug
+===================
+
+If you think you've discovered a bug, please read through these
+guidelines before reporting it.
+
+First, make sure it's a new bug:
+
+* Please check the list of `known bugs`_ below and the `SourceForge
+  Bug Tracker`_ to see if it has already been reported.
+
+* Are you using the very latest version of Docutils?  The bug may have
+  already been fixed.  Please get the latest version of Docutils from
+  CVS_ or from the `development snapshot`_ and check again.  Even if
+  your bug has not been fixed, others probably have, and you're better
+  off with the most up-to-date code.
+
+  If you don't have time to check the latest snapshot, please report
+  the bug anyway.  We'd rather tell you that it's already fixed than
+  miss reports of unfixed bugs.
+
+* If Docutils does not behave the way you expect, look in the
+  documentation_ (don't forget the FAQ_!) and `mailing list archives`_
+  for evidence that it should behave the way you expect.
+
+If you're not sure, please ask on the
+docutils-users at lists.sourceforge.net [1]_ mailing list first.
+
+If it's a new bug, the most important thing you can do is to write a
+simple description and a recipe that reproduces the bug.  Try to
+create a minimal document that demonstrates the bug.  The easier you
+make it to understand and track down the bug, the more likely a fix
+will be.
+
+Now you're ready to write the bug report.  Please include:
+
+* A clear description of the bug.  Describe how you expected Docutils
+  to behave, and contrast that with how it actually behaved.  While
+  the bug may seem obvious to you, it may not be so obvious to someone
+  else, so it's best to avoid a guessing game.
+
+* A complete description of the environment in which you reproduced
+  the bug:
+
+  - Your operating system & version.
+  - The version of Python (``python -V``).
+  - The version of Docutils (use the "-V" option to most Docutils
+    front-end tools).
+  - Any private modifications you made to Docutils.
+  - Anything else that could possibly be relevant.  Err on the side
+    of too much information, rather than too little.
+
+* A literal transcript of the *exact* command you ran, and the *exact*
+  output.  Use the "--traceback" option to get a complete picture.
+
+* The exact input and output files.  Better to attach complete files
+  to your bug report than to include just a summary or excerpt.
+
+* If you also want to include speculation as to the cause, and even a
+  patch to fix the bug, that would be great!
+
+The best place to send your bug report is to the `SourceForge Bug
+Tracker`_.  That way, it won't be misplaced or forgotten.  In fact, an
+open bug report on SourceForge is a constant irritant that begs to be
+squashed.
+
+Thank you!
+
+(This section was inspired by the `Subversion project's`__ BUGS__
+file.)
+
+.. [1] Due to overwhelming amounts of spam, the
+   docutils-users at lists.sourceforge.net mailing list has been set up
+   for subscriber posting only.  Non-subscribers who post to
+   docutils-users will receive a message with "Subject: Your message
+   to Docutils-users awaits moderator approval".  Legitimate messages
+   are accepted and posted as soon as possible (a list administrator
+   must verify the message manually).  If you'd like to subscribe to
+   docutils-users, please visit
+   <http://lists.sourceforge.net/lists/listinfo/docutils-users>.
+
+__ http://subversion.tigris.org/
+__ http://svn.collab.net/viewcvs/svn/trunk/BUGS?view=markup
+
+.. _CVS: http://sourceforge.net/cvs/?group_id=38414
+.. _development snapshot: http://docutils.sf.net/#development-snapshot
+.. _documentation: docs/
+.. _FAQ: FAQ.html
+.. _mailing list archives: http://docutils.sf.net/#mailing-lists
+.. _SourceForge Bug Tracker:
+   http://sourceforge.net/tracker/?group_id=38414&atid=422030
+
+
+Known Bugs
+==========
+
+Also see the `SourceForge Bug Tracker`_.
+
+* .. _unencoded stylesheet reference:
+
+  ``--stylesheet='foo&bar'`` causes an invalid stylesheet reference to
+  be inserted in an HTML file, because the ``&`` isn't encoded as
+  ``&amp;``.
+
+* ``utils.relative_path()`` sometimes returns absolute _`paths on
+  Windows` (like ``C:/test/foo.css``) where it could have chosen a
+  relative path.
+
+  Furthermore, absolute pathnames are inserted verbatim, like
+  ``href="C:/test/foo.css"`` instead of
+  ``href="file:///C:/test/foo.css"``.
+
+  For details, see `this posting by Alan G. Isaac
+  <http://article.gmane.org/gmane.text.docutils.user/1569>`_.
+
+* .. _empty csv-table:
+
+  When supplying an empty file for the csv-table directive, Docutils
+  crashes with a zero-division error.  Example::
+
+      .. csv-table::
+         :file: empty.txt
+
+* _`Line numbers` in system messages are inconsistent in the parser.
+
+  - In text inserted by the "include" directive, errors are often not
+    reported with the correct "source" or "line" numbers.  Perhaps all
+    Reporter calls need "source" and "line" keyword arguments.
+    Elements' .line assignments should be checked.  (Assign to .source
+    too?  Add a set_info method?  To what?)  There's a test in
+    test/test_parsers/test_rst/test_directives/test_include.py.
+
+  - Some line numbers in elements are not being set properly
+    (explicitly), just implicitly/automatically.  See rev. 1.74 of
+    docutils/parsers/rst/states.py for an example of how to set.
+
+* .. _none source:
+
+  Quite a few nodes are getting a "None" source attribute as well.  In
+  particular, see the bodies of definition lists.
+
+* .. _mislocated targets:
+
+  Explicit targets are sometimes mis-located.  In particular, placing
+  a target before a section header puts the target at the end of the
+  previous section instead of the start of the next section.  The code
+  in docutils.transforms.misc.ClassAttribute could be used to fix
+  this.  (Reported by David Priest.)
+
+* David Abrahams pointed out that _`doubly-indirect substitutions`
+  have a bug, but only when there's multiple references::
+
+      |substitute| my coke for gin
+      |substitute| you for my mum
+      at least I'll get my washing done
+
+      .. |substitute| replace:: |replace|
+      .. |replace| replace:: swap
+
+  This is tricky.  Substitutions have to propagate back completely.
+
+* .. _substitutions and references:
+
+  Another bug from David Abrahams (run with ``rst2html.py --traceback``)::
+
+      |substitution| and again a |substitution|.
+
+      .. |substitution| replace:: ref__
+
+      __ a.html
+      __ b.html
+
+  Change the references.Substitutions tranform's priority from 220 to
+  680, so it happens after reference resolution?  Then we have to deal
+  with multiple IDs.  Perhaps the Substitution transform should remove
+  all IDs from definitions after the first substitution reference is
+  processed.
+
+* Footnote label "5" should be "4"::
+
+      $ rst2pseudoxml.py <<EOF
+      > ref [#abc]_ [#]_ [1]_ [#4]_
+      > 
+      > .. [#abc] footnote
+      > .. [#] two
+      > .. [1] one
+      > .. [#4] four
+      > EOF
+      <document source="<stdin>">
+          <paragraph>
+              ref 
+              <footnote_reference auto="1" id="id1" refid="abc">
+                  2
+               
+              <footnote_reference auto="1" id="id2" refid="id5">
+                  3
+               
+              <footnote_reference id="id3" refid="id6">
+                  1
+               
+              <footnote_reference auto="1" id="id4" refid="id7">
+                  5
+          <footnote auto="1" backrefs="id1" id="abc" name="abc">
+              <label>
+                  2
+              <paragraph>
+                  footnote
+          <footnote auto="1" backrefs="id2" id="id5" name="3">
+              <label>
+                  3
+              <paragraph>
+                  two
+          <footnote backrefs="id3" id="id6" name="1">
+              <label>
+                  1
+              <paragraph>
+                  one
+          <footnote auto="1" backrefs="id4" id="id7" name="4">
+              <label>
+                  5
+              <paragraph>
+                  four
+
+
+..
+   Local Variables:
+   mode: indented-text
+   indent-tabs-mode: nil
+   sentence-end-double-space: t
+   fill-column: 70
+   End:

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/COPYING.txt
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/COPYING.txt	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/COPYING.txt	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,124 @@
+==================
+ Copying Docutils
+==================
+
+:Author: David Goodger
+:Contact: goodger at users.sourceforge.net
+:Date: $Date: 2005/01/07 15:11:43 $
+:Web site: http://docutils.sourceforge.net/
+:Copyright: This document has been placed in the public domain.
+
+Most of the files included in this project have been placed in the
+public domain, and therefore have no license requirements and no
+restrictions on copying or usage; see the `Public Domain Dedication`_
+below.  There are a few exceptions_, listed below.
+
+One goal of the Docutils project is to be included in the Python
+standard library distribution, at which time it is expected that
+copyright will be asserted by the `Python Software Foundation
+<http://www.python.org/psf/>`_.
+
+
+Public Domain Dedication
+========================
+
+The persons who have associated their work with this project (the
+"Dedicator": David Goodger and the many contributors to the Docutils
+project) hereby dedicate the entire copyright, less the exceptions_
+listed below, in the work of authorship known as "Docutils" identified
+below (the "Work") to the public domain.
+
+The primary repository for the Work is the Internet World Wide Web
+site <http://docutils.sourceforge.net/>.  The Work consists of the
+files within the "docutils" module of the Docutils project CVS
+repository (Internet host cvs.sourceforge.net, filesystem path
+/cvsroot/docutils), whose Internet web interface is located at
+<http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/docutils/docutils/>.
+Files dedicated to the public domain may be identified by the
+inclusion, near the beginning of each file, of a declaration of the
+form::
+
+    Copyright: This document/module/DTD/stylesheet/file/etc. has been
+               placed in the public domain.
+
+Dedicator makes this dedication for the benefit of the public at large
+and to the detriment of Dedicator's heirs and successors.  Dedicator
+intends this dedication to be an overt act of relinquishment in
+perpetuity of all present and future rights under copyright law,
+whether vested or contingent, in the Work.  Dedicator understands that
+such relinquishment of all rights includes the relinquishment of all
+rights to enforce (by lawsuit or otherwise) those copyrights in the
+Work.
+
+Dedicator recognizes that, once placed in the public domain, the Work
+may be freely reproduced, distributed, transmitted, used, modified,
+built upon, or otherwise exploited by anyone for any purpose,
+commercial or non-commercial, and in any way, including by methods
+that have not yet been invented or conceived.
+
+(This dedication is derived from the text of the `Creative Commons
+Public Domain Dedication
+<http://creativecommons.org/licenses/publicdomain>`_.)
+
+
+Exceptions
+==========
+
+The exceptions to the `Public Domain Dedication`_ above are:
+
+* extras/optparse.py, copyright by Gregory P. Ward, released under a
+  BSD-style license (which can be found in the module's source code).
+
+* extras/textwrap.py, copyright by Gregory P. Ward and the Python
+  Software Foundation, released under the `Python 2.3 license`_
+  (`local copy`__).
+
+  __ licenses/python-2-3.txt
+
+* extras/roman.py, copyright by Mark Pilgrim, released under the
+  `Python 2.1.1 license`_ (`local copy`__).
+
+  __ licenses/python-2-1-1.txt
+
+* test/docutils_difflib.py, copyright by the Python Software
+  Foundation, released under the `Python 2.2 license`_ (`local
+  copy`__).  This file is included for compatibility with Python
+  versions less than 2.2.  (It's only used to report test failures
+  anyhow; it isn't installed anywhere.  The included file is a
+  pre-generator version of the difflib.py module included in Python
+  2.2.)
+
+  __ licenses/python-2-2.txt
+
+* tools/pep2html.py, copyright by the Python Software Foundation,
+  released under the `Python 2.2 license`_ (`local copy`__).
+
+  __ licenses/python-2-2.txt
+
+* tools/editors/emacs/rst-html.el, copyright by Martin Blais, released
+  under the `GNU General Public License`_ (`local copy`__).
+
+  __ licenses/gpl.txt
+
+* tools/editors/emacs/rst-mode.el, copyright by Stefan Merten,
+  released under the `GNU General Public License`_ (`local copy`__).
+
+  __ licenses/gpl.txt
+
+(Disclaimer: I am not a lawyer.)  The BSD license and the Python
+licenses are OSI-approved_ and GPL-compatible_.  Although complicated
+by multiple owners and lots of legalese, the Python license basically
+lets you copy, use, modify, and redistribute files as long as you keep
+the copyright attribution intact, note any changes you make, and don't
+use the owner's name in vain.  The BSD license is similar.
+
+Plaintext versions of all the linked-to licenses are provided in the
+licenses_ directory.
+
+.. _licenses: licenses/
+.. _Python 2.1.1 license: http://www.python.org/2.1.1/license.html
+.. _Python 2.2 license: http://www.python.org/2.2/license.html
+.. _Python 2.3 license: http://www.python.org/2.3/license.html
+.. _GNU General Public License: http://www.gnu.org/copyleft/gpl.html
+.. _OSI-approved: http://opensource.org/licenses/
+.. _GPL-compatible: http://www.gnu.org/philosophy/license-list.html

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/FAQ.txt
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/FAQ.txt	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/FAQ.txt	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,937 @@
+===========================================
+ Docutils FAQ (Frequently Asked Questions)
+===========================================
+
+:Date: $Date: 2005/01/07 15:11:43 $
+:Web site: http://docutils.sourceforge.net/
+:Copyright: This document has been placed in the public domain.
+
+.. Please note that until there's a Q&A-specific construct available,
+   this FAQ will use section titles for questions.  Therefore
+   questions must fit on one line.  The title may be a summary of the
+   question, with the full question in the section body.
+
+
+.. contents::
+.. sectnum::
+
+
+This is a work in progress.  Please feel free to ask questions and/or
+provide answers; `send email`__ to the `Docutils-Users mailing list`__
+[1]_.  Project members should feel free to edit the source text file
+directly.
+
+.. [1] Due to overwhelming amounts of spam, the
+   docutils-users at lists.sourceforge.net mailing list has been set up
+   for subscriber posting only.  Non-subscribers who post to
+   docutils-users will receive a message with "Subject: Your message
+   to Docutils-users awaits moderator approval".  Legitimate messages
+   are accepted and posted as soon as possible (a list administrator
+   must verify the message manually).  If you'd like to subscribe to
+   docutils-users, please visit
+   <http://lists.sourceforge.net/lists/listinfo/docutils-users>.
+
+.. _let us know:
+__ mailto:docutils-users at lists.sourceforge.net
+__ http://lists.sourceforge.net/lists/listinfo/docutils-users
+
+
+Docutils
+========
+
+What is Docutils?
+-----------------
+
+Docutils_ is a system for processing plaintext documentation into
+useful formats, such as HTML, XML, and LaTeX.  It supports multiple
+types of input, such as standalone files (implemented), inline
+documentation from Python modules and packages (under development),
+`PEPs (Python Enhancement Proposals)`_ (implemented), and others as
+discovered.
+
+For an overview of the Docutils project implementation, see `PEP
+258`_, "Docutils Design Specification".
+
+Docutils is implemented in Python_.
+
+.. _Docutils: http://docutils.sourceforge.net/
+.. _PEPs (Python Enhancement Proposals):
+   http://www.python.org/peps/pep-0012.html
+.. _PEP 258: http://www.python.org/peps/pep-0258.html
+.. _Python: http://www.python.org/
+
+
+Why is it called "Docutils"?
+----------------------------
+
+Docutils is short for "Python Documentation Utilities".  The name
+"Docutils" was inspired by "Distutils", the Python Distribution
+Utilities architected by Greg Ward, a component of Python's standard
+library.
+
+The earliest known use of the term "docutils" in a Python context was
+a `fleeting reference`__ in a message by Fred Drake on 1999-12-02 in
+the Python Doc-SIG mailing list.  It was suggested `as a project
+name`__ on 2000-11-27 on Doc-SIG, again by Fred Drake, in response to
+a question from Tony "Tibs" Ibbs: "What do we want to *call* this
+thing?".  This was shortly after David Goodger first `announced
+reStructuredText`__ on Doc-SIG.
+
+Tibs used the name "Docutils" for `his effort`__ "to document what the
+Python docutils package should support, with a particular emphasis on
+documentation strings".  Tibs joined the current project (and its
+predecessors) and graciously donated the name.
+
+For more history of reStructuredText and the Docutils project, see `An
+Introduction to reStructuredText`_.
+
+Please note that the name is "Docutils", not "DocUtils" or "Doc-Utils"
+or any other variation.
+
+.. _An Introduction to reStructuredText:
+   http://docutils.sourceforge.net/docs/ref/rst/introduction.html
+__ http://mail.python.org/pipermail/doc-sig/1999-December/000878.html
+__ http://mail.python.org/pipermail/doc-sig/2000-November/001252.html
+__ http://mail.python.org/pipermail/doc-sig/2000-November/001239.html
+__ http://homepage.ntlworld.com/tibsnjoan/docutils/STpy.html
+
+
+Is there a GUI authoring environment for Docutils?
+--------------------------------------------------
+
+DocFactory_ is under development.  It uses wxPython and looks very
+promising.
+
+.. _DocFactory:
+   http://docutils.sf.net/sandbox/gschwant/docfactory/doc/
+
+
+What is the status of the Docutils project?
+-------------------------------------------
+
+Although useful and relatively stable, Docutils is experimental code,
+with APIs and architecture subject to change.
+
+Our highest priority is to fix bugs as they are reported.  So the
+latest code from CVS (or `development snapshots`_) is almost always
+the most stable (bug-free) as well as the most featureful.
+
+
+What is the Docutils project release policy?
+--------------------------------------------
+
+It ought to be "release early & often", but official releases are a
+significant effort and aren't done that often.  We have
+automatically-generated `development snapshots`_ which always contain
+the latest code from CVS.  As the project matures, we may formalize on
+a stable/development-branch scheme, but we're not using anything like
+that yet.
+
+.. _development snapshots:
+   http://docutils.sourceforge.net/#development-snapshots
+
+
+reStructuredText
+================
+
+What is reStructuredText?
+-------------------------
+
+reStructuredText_ is an easy-to-read, what-you-see-is-what-you-get
+plaintext markup syntax and parser system.  The reStructuredText
+parser is a component of Docutils_.  reStructuredText is a revision
+and reinterpretation of the StructuredText_ and Setext_ lightweight
+markup systems.
+
+If you are reading this on the web, you can see for yourself.  `The
+source for this FAQ <FAQ.txt>`_ is written in reStructuredText; open
+it in another window and compare them side by side.
+
+`A ReStructuredText Primer`_ and the `Quick reStructuredText`_ user
+reference are a good place to start.  The `reStructuredText Markup
+Specification`_ is a detailed technical specification.
+
+.. _A ReStructuredText Primer:
+   http://docutils.sourceforge.net/docs/user/rst/quickstart.html
+.. _Quick reStructuredText:
+   http://docutils.sourceforge.net/docs/user/rst/quickref.html
+.. _reStructuredText Markup Specification:
+   http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html
+.. _reStructuredText: http://docutils.sourceforge.net/rst.html
+.. _StructuredText:
+   http://dev.zope.org/Members/jim/StructuredTextWiki/FrontPage/
+.. _Setext: http://docutils.sourceforge.net/mirror/setext.html
+
+
+Why is it called "reStructuredText"?
+------------------------------------
+
+The name came from a combination of "StructuredText", one of
+reStructuredText's predecessors, with "re": "revised", "reworked", and
+"reinterpreted", and as in the ``re.py`` regular expression module.
+For a detailed history of reStructuredText and the Docutils project,
+see `An Introduction to reStructuredText`_.
+
+
+What's the standard abbreviation for "reStructuredText"?
+--------------------------------------------------------
+
+"RST" and "ReST" (or "reST") are both acceptable.  Care should be
+taken with capitalization, to avoid confusion with "REST__", an
+acronym for "Representational State Transfer".
+
+The abbreviations "reSTX" and "rSTX"/"rstx" should **not** be used;
+they overemphasize reStructuredText's precedessor, Zope's
+StructuredText.
+
+__ http://www.xml.com/pub/a/2002/02/06/rest.html
+
+
+What's the standard filename extension for a reStructuredText file?
+-------------------------------------------------------------------
+
+It's ".txt".  Some people would like to use ".rest" or ".rst" or
+".restx", but why bother?  ReStructuredText source files are meant to
+be readable as plaintext, and most operating systems already associate
+".txt" with text files.  Using a specialized filename extension would
+require that users alter their OS settings, which is something that
+many users will not be willing or able to do.
+
+
+Are there any reStructuredText editor extensions?
+-------------------------------------------------
+
+See `Editor Support for reStructuredText`__.
+
+__ http://docutils.sf.net/tools/editors/README.html
+
+
+How can I indicate the document title?  Subtitle?
+-------------------------------------------------
+
+A uniquely-adorned section title at the beginning of a document is
+treated specially, as the document title.  Similarly, a
+uniquely-adorned section title immediately after the document title
+becomes the document subtitle.  For example::
+
+    This is the Document Title
+    ==========================
+
+    This is the Document Subtitle
+    -----------------------------
+
+    Here's an ordinary paragraph.
+
+Counterexample::
+
+    Here's an ordinary paragraph.
+
+    This is *not* a Document Title
+    ==============================
+
+    The "ordinary paragraph" above the section title
+    prevents it from becoming the document title.
+
+Another counterexample::
+
+    This is not the Document Title,  because...
+    ===========================================
+
+    Here's an ordinary paragraph.
+
+    ... the title adornment is not unique
+    =====================================
+
+    Another ordinary paragraph.
+
+
+How can I represent esoteric characters (e.g. character entities) in a document?
+--------------------------------------------------------------------------------
+
+For example, say you want an em-dash (XML character entity &mdash;,
+Unicode character ``\u2014``) in your document: use a real em-dash.
+Insert concrete characters (e.g. type a *real* em-dash) into your
+input file, using whatever encoding suits your application, and tell
+Docutils the input encoding.  Docutils uses Unicode internally, so the
+em-dash character is a real em-dash internally.
+
+Emacs users should refer to the `Emacs Support for reStructuredText`__
+document.  Tips for other editors are welcome.
+
+__ http://docutils.sourceforge.net/tools/editors/emacs/README.html
+
+ReStructuredText has no character entity subsystem; it doesn't know
+anything about XML charents.  To Docutils, "&mdash;" in input text is
+7 discrete characters; no interpretation happens.  When writing HTML,
+the "&" is converted to "&amp;", so in the raw output you'd see
+"&amp;mdash;".  There's no difference in interpretation for text
+inside or outside inline literals or literal blocks -- there's no
+character entity interpretation in either case.
+
+If you can't use a Unicode-compatible encoding and must rely on 7-bit
+ASCII, there is a workaround.  Files containing character entity set
+substitution definitions using the "unicode_" directive `are
+available`_ (tarball_).  Please read the `description and
+instructions`_ for use.  Thanks to David Priest for the original idea.
+Incorporating these files into Docutils is on the `to-do list`_.
+   
+If you insist on using XML-style charents, you'll have to implement a
+pre-processing system to convert to UTF-8 or something.  That
+introduces complications though; you can no longer *write* about
+charents naturally; instead of writing "&mdash;" you'd have to write
+"&amp;mdash;".
+
+For the common case of long dashes, you might also want to insert the
+following substitution definitons into your document (both of them are
+using the "unicode_" directive)::
+
+    .. |--| unicode:: U+2013   .. en dash
+    .. |---| unicode:: U+2014  .. em dash, trimming surrounding whitespace
+       :trim:
+
+.. |--| unicode:: U+2013   .. en dash
+.. |---| unicode:: U+2014  .. em dash, trimming surrounding whitespace
+   :trim:
+
+Now you can write dashes using pure ASCII: "``foo |--| bar; foo |---|
+bar``", rendered as "foo |--| bar; foo |---| bar".  (Note that Mozilla
+and Firefox may render this incorrectly.)  The ``:trim:`` option for
+the em dash is necessary because you cannot write "``foo|---|bar``";
+thus you need to add spaces ("``foo |---| bar``") and advise the
+reStructuredText parser to trim the spaces using the ``:trim:``
+option.
+
+.. _unicode:
+   http://docutils.sf.net/docs/ref/rst/directives.html#unicode-character-codes
+.. _are available: http://docutils.sourceforge.net/tmp/charents/
+.. _tarball: http://docutils.sourceforge.net/tmp/charents.tgz
+.. _description and instructions:
+   http://docutils.sourceforge.net/tmp/charents/README.html
+.. _to-do list: http://docutils.sourceforge.net/docs/dev/todo.html
+
+
+How can I generate backticks using a Scandinavian keyboard?
+-----------------------------------------------------------
+
+The use of backticks in reStructuredText is a bit awkward with
+Scandinavian keyboards, where the backtick is a "dead" key.  To get
+one ` character one must press SHIFT-` + SPACE.
+
+Unfortunately, with all the variations out there, there's no way to
+please everyone.  For Scandinavian programmers and technical writers,
+this is not limited to reStructuredText but affects many languages and
+environments.
+
+Possible solutions include
+
+* If you have to input a lot of backticks, simply type one in the
+  normal/awkward way, select it, copy and then paste the rest (CTRL-V
+  is a lot faster than SHIFT-` + SPACE).
+
+* Use keyboard macros.
+
+* Remap the keyboard.  The Scandinavian keyboard layout is awkward for
+  other programming/technical characters too; for example, []{}
+  etc. are a bit awkward compared to US keyboards.
+
+  According to Axel Kollmorgen,
+
+      Under Windows, you can use the `Microsoft Keyboard Layout Creator
+      <http://www.microsoft.com/globaldev/tools/msklc.mspx>`__ to easily
+      map the backtick key to a real backtick (no dead key). took me
+      five minutes to load my default (german) keyboard layout, untick
+      "Dead Key?" from the backtick key properties ("in all shift
+      states"), "build dll and setup package", install the generated
+      .msi, and add my custom keyboard layout via Control Panel >
+      Regional and Language Options > Languages > Details > Add
+      Keyboard layout (and setting it as default "when you start your
+      computer").
+
+* Use a virtual/screen keyboard or character palette, such as:
+
+  - `Web-based keyboards <http://keyboard.lab.co.il/>`__ (IE only
+    unfortunately).
+  - Windows: `Click-N-Type <http://www.lakefolks.org/cnt/>`__.
+  - Mac OS X: the Character Palette can store a set of favorite
+    characters for easy input.  Open System Preferences,
+    International, Input Menu tab, enable "Show input menu in menu
+    bar", and be sure that Character Palette is enabled in the list.
+
+  Other options are welcome.
+
+If anyone knows of other/better solutions, please `let us know`_.
+
+
+Are there any tools for HTML/XML-to-reStructuredText?  (Round-tripping)
+-----------------------------------------------------------------------
+
+People have tossed the idea around, but little if any actual work has
+ever been done.  There's no reason why reStructuredText should not be
+round-trippable to/from XML; any technicalities which prevent
+round-tripping would be considered bugs.  Whitespace would not be
+identical, but paragraphs shouldn't suffer.  The tricky parts would be
+the smaller details, like links and IDs and other bookkeeping.
+
+For HTML, true round-tripping may not be possible.  Even adding lots
+of extra "class" attributes may not be enough.  A "simple HTML" to RST
+filter is possible -- for some definition of "simple HTML" -- but HTML
+is used as dumb formatting so much that such a filter may not be
+particularly useful.  No general-purpose filter exists.  An 80/20
+approach should work though: build a tool that does 80% of the work
+automatically, leaving the other 20% for manual tweaks.
+
+
+Are there any Wikis that use reStructuredText syntax?
+-----------------------------------------------------
+
+There are several, with various degrees of completeness.  With no
+implied endorsement or recommendation, and in no particular order:
+
+* `Ian Bicking's experimental code
+  <http://docutils.sf.net/sandbox/ianb/wiki/WikiPage.py>`__
+* `MoinMoin <http://moin.sf.net>`__ has some support; `here's a sample
+  <http://twistedmatrix.com/users/jh.twistd/moin/moin.cgi/RestSample>`__
+* Zope-based `Zwiki <http://zwiki.org/>`__
+* Zope3-based Zwiki (in the Zope 3 source tree as ``zope.products.zwiki``)
+* `StikiWiki <http://mithrandr.moria.org/code/stikiwiki/>`__
+* `Trac <http://projects.edgewall.com/trac/>`__ `supports using reStructuredText
+  <http://projects.edgewall.com/trac/wiki/WikiRestructuredText>`__ as an
+  alternative to wiki markup. This includes support for `TracLinks
+  <http://projects.edgewall.com/trac/wiki/TracLinks>`__ from within RST
+  text via a custom RST reference-directive or, even easier, an interpreted text
+  role 'trac'
+* `Vogontia <http://www.ososo.de/vogontia/>`__, a Wiki-like FAQ system
+
+Please `let us know`_ of any other reStructuredText Wikis.
+
+The example application for the `Web Framework Shootout
+<http://colorstudy.com/docs/shootout.html>`__ article is a Wiki using
+reStructuredText.
+
+
+Are there any Weblog (Blog) projects that use reStructuredText syntax?
+----------------------------------------------------------------------
+
+With no implied endorsement or recommendation, and in no particular
+order:
+
+* `Python Desktop Server <http://pyds.muensterland.org/>`__
+* `PyBloxsom <http://roughingit.subtlehints.net/pyblosxom/>`__
+* `rst2ht <http://www.rutherfurd.net/articles/rst-ht2html.html>`__
+* `htmlnav <http://docutils.sourceforge.net/sandbox/gschwant/htmlnav/>`__
+* `restblog <http://docutils.sourceforge.net/sandbox/mly/restblog/>`__
+* `ReSTWeb <http://wingide.com/opensource/restweb>`__
+* `Lino WebMan <http://lino.sourceforge.net/webman.html>`__
+
+Please `let us know`_ of any other reStructuredText Blogs.
+
+
+Can lists be indented without generating block quotes?
+------------------------------------------------------
+
+Some people like to write lists with indentation, without intending a
+block quote context, like this::
+
+    paragraph
+
+      * list item 1
+      * list item 2
+
+There has been a lot of discussion about this, but there are some
+issues that would need to be resolved before it could be implemented.
+There is a summary of the issues and pointers to the discussions in
+`the to-do list`__.
+
+__ http://docutils.sourceforge.net/docs/dev/todo.html#indented-lists
+
+
+Could the requirement for blank lines around lists be relaxed?
+--------------------------------------------------------------
+
+Short answer: no.
+
+In reStructuredText, it would be impossible to unambigously mark up
+and parse lists without blank lines before and after.  Deeply nested
+lists may look ugly with so many blank lines, but it's a price we pay
+for unambiguous markup.  Some other plaintext markup systems do not
+require blank lines in nested lists, but they have to compromise
+somehow, either accepting ambiguity or requiring extra complexity.
+For example, `Epytext <http://epydoc.sf.net/epytext.html#list>`__ does
+not require blank lines around lists, but it does require that lists
+be indented and that ambiguous cases be escaped.
+
+
+How can I include mathematical equations in documents?
+------------------------------------------------------
+
+There is no elegant built-in way, yet.  There are several ideas, but
+no obvious winner.  This issue requires a champion to solve the
+technical and aesthetic issues and implement a generic solution.
+Here's the `to-do list entry`__.
+
+__ http://docutils.sourceforge.net/docs/dev/todo.html#math-markup
+
+There are several quick & dirty ways to include equations in documents.
+They all presently use LaTeX syntax or dialects of it.
+
+* For LaTeX output, nothing beats raw LaTeX math.  A simple way is to
+  use the `raw directive`_::
+
+      .. raw:: latex
+
+          \[ x^3 + 3x^2a + 3xa^2 + a^3, \]
+          
+  For inline math you could use substitutions of the raw directive but
+  the recently added `raw role`_ is more convenient.  You must define a
+  custom role based on it once in your document::
+
+      .. role:: raw-latex(raw)
+          :format: latex
+
+  and then you can just use the new role in your text::
+
+      the binomial expansion of :raw-latex:`$(x+a)^3$` is
+
+  .. _raw directive: http://docutils.sourceforge.net/docs/ref/rst/
+                     directives.html#raw-data-pass-through
+  .. _raw role: http://docutils.sourceforge.net/docs/ref/rst/roles.html#raw
+
+* For HTML the "Right" w3c-standard way to include math is MathML_.
+  Unfortunately its rendering is still quite broken (or absent) on many
+  browsers but it's getting better.  Another bad problem is that typing
+  or reading raw MathML by humans is *really* painful, so embedding it
+  in a reST document with the raw directive would defy the goals of
+  readability and editability of reST (see an `example of raw MathML
+  <http://sf.net/mailarchive/message.php?msg_id=2177102>`__).
+
+  A much less painful way to generate HTML+MathML is to use itex2mml_ to
+  convert a dialect of LaTeX syntax to presentation MathML.  Here is an
+  example of potential `itex math markup
+  <http://article.gmane.org/gmane.text.docutils.user/118>`__.  The
+  simplest way to use it is to add ``html`` to the format lists for the
+  raw directive/role and postprocess the resulting document with
+  itex2mml.  This way you can *generate LaTeX and HTML+MathML from the
+  same source*, but you must limit yourself to the intersection of LaTeX
+  and itex markups for this to work.  Alan G. Isaac wrote a detailed
+  HOWTO_ for this approach.
+
+  .. _MathML: http://www.w3.org/Math/
+  .. _itex2mml: http://pear.math.pitt.edu/mathzilla/itex2mml.html
+  .. _HOWTO: http://www.american.edu/econ/itex2mml/mathhack.rst
+
+  * The other way to add math to HTML is to use images of the equations,
+    typically generated by TeX.  This is inferior to MathML in the long
+    term but is perhaps more accessible nowdays.
+
+    Of course, doing it by hand is too much work.  Beni Cherniavsky has
+    written some `preprocessing scripts`__ for converting the
+    ``texmath`` role/directive into images for HTML output and raw
+    directives/subsitution for LaTeX output.  This way you can *generate
+    LaTeX and HTML+images from the same source*.  `Instructions here`__.
+
+    __ http://docutils.sourceforge.net/sandbox/cben/rolehack/
+    __ http://docutils.sourceforge.net/sandbox/cben/rolehack/README.html
+
+
+Is nested inline markup possible?
+---------------------------------
+
+Not currently, no.  It's on the `to-do list`__ (`details here`__), and
+hopefully will be part of the reStructuredText parser soon.  At that
+time, markup like this will become possible::
+
+    Here is some *emphasized text containing a `hyperlink`_ and
+    ``inline literals``*.
+
+__ http://docutils.sf.net/docs/dev/todo.html#nested-inline-markup
+__ http://docutils.sf.net/docs/dev/rst/alternatives.html#nested-inline-markup
+
+There are workarounds, but they are either convoluted or ugly or both.
+They are not recommended.
+
+* Inline markup can be combined with hyperlinks using `substitution
+  definitions`__ and references__ with the `"replace" directive`__.
+  For example::
+
+      Here is an |emphasized hyperlink|_.
+
+      .. |emphasized hyperlink| replace:: *emphasized hyperlink*
+      .. _emphasized hyperlink: http://example.org
+
+  It is not possible for just a portion of the replacement text to be
+  a hyperlink; it's the entire replacement text or nothing.
+
+  __ http://docutils.sf.net/docs/ref/rst/restructuredtext.html#substitution-definitions
+  __ http://docutils.sf.net/docs/ref/rst/restructuredtext.html#substitution-references
+  __ http://docutils.sf.net/docs/ref/rst/directives.html#replace
+
+* The `"raw" directive`__ can be used to insert raw HTML into HTML
+  output::
+
+      Here is some |stuff|.
+
+      .. |stuff| raw:: html
+
+         <em>emphasized text containing a
+         <a href="http://example.org">hyperlink</a> and
+         <tt>inline literals</tt></em>
+
+  Raw LaTeX is supported for LaTeX output, etc.
+
+  __ http://docutils.sf.net/docs/ref/rst/directives.html#raw
+
+
+How to indicate a line break or a significant newline?
+------------------------------------------------------
+
+`Line blocks`__ are designed for address blocks, verse, and other
+cases where line breaks are significant and must be preserved.  Unlike
+literal blocks, the typeface is not changed, and inline markup is
+recognized.  For example::
+
+    | A one, two, a one two three four
+    | 
+    | Half a bee, philosophically,
+    |     must, *ipso facto*, half not be.
+    | But half the bee has got to be,
+    |     *vis a vis* its entity.  D'you see?
+    | 
+    | But can a bee be said to be
+    |     or not to be an entire bee,
+    |         when half the bee is not a bee,
+    |             due to some ancient injury?
+    | 
+    | Singing...
+
+__ http://docutils.sf.net/docs/ref/rst/restructuredtext.html#line-blocks
+
+Here's a workaround for manually inserting explicit line breaks in
+HTML output::
+
+    .. |br| raw:: html
+
+       <br />
+
+    I want to break this line here: |br| this is after the break.
+
+    If the extra whitespace bothers you, |br|\ backslash-escape it.
+
+
+A URL containing asterisks doesn't work.  What to do?
+-----------------------------------------------------
+
+Asterisks are valid URL characters (see :RFC:`2396`), sometimes used
+in URLs.  For example::
+
+    http://cvs.example.org/viewcvs.py/*checkout*/module/file
+
+Unfortunately, the parser thinks the asterisks are indicating
+emphasis.  The slashes serve as delineating punctuation, allowing the
+asterisks to be recognized as markup.  The example above is separated
+by the parser into a truncated URL, an emphasized word, and some
+regular text::
+
+    http://cvs.example.org/viewcvs.py/
+    *checkout*
+    /module/file
+
+To turn off markup recognition, use a backslash to escape at least the
+first asterisk, like this::
+
+    http://cvs.example.org/viewcvs.py/\*checkout*/module/file
+
+Escaping the second asterisk doesn't hurt, but it isn't necessary.
+
+
+How can I make a literal block with *some* formatting?
+------------------------------------------------------
+
+Use the `parsed-literal`_ directive.
+
+.. _parsed-literal: docs/ref/rst/directives.html#parsed-literal
+
+Scenario: a document contains some source code, which calls for a
+literal block to preserve linebreaks and whitespace.  But part of the
+source code should be formatted, for example as emphasis or as a
+hyperlink.  This calls for a *parsed* literal block::
+
+    .. parsed-literal::
+
+       print "Hello world!"  # *tricky* code [1]_
+
+The emphasis (``*tricky*``) and footnote reference (``[1]_``) will be
+parsed.
+
+
+Can reStructuredText be used for web or generic templating?
+-----------------------------------------------------------
+
+Docutils and reStructuredText can be used with or as a component of a
+templating system, but they do not themselves include templating
+functionality.  Templating should simply be left to dedicated
+templating systems.  Users can choose a templating system to apply to
+their reStructuredText documents as best serves their interests.
+
+There are many good templating systems for Python (ht2html_, YAPTU_,
+Quixote_'s PTL, Cheetah_, etc.; see this non-exhaustive list of `some
+other templating systems`_), and many more for other languages, each
+with different approaches.  We invite you to try several and find one
+you like.  If you adapt it to use Docutils/reStructuredText, please
+consider contributing the code to Docutils or `let us know`_ and we'll
+keep a list here.
+
+.. _ht2html: http://ht2html.sourceforge.net/
+.. _YAPTU:
+   http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52305
+.. _Quixote: http://www.mems-exchange.org/software/quixote/
+.. _Cheetah: http://www.cheetahtemplate.org/
+.. _some other templating systems:
+   http://webware.sourceforge.net/Papers/Templates/
+
+
+HTML Writer
+===========
+
+What is the status of the HTML Writer?
+--------------------------------------
+
+The HTML Writer module, ``docutils/writers/html4css1.py``, is a
+proof-of-concept reference implementation.  While it is a complete
+implementation, some aspects of the HTML it produces may be
+incompatible with older browsers or specialized applications (such as
+web templating).  Alternate implementations are welcome.
+
+
+What kind of HTML does it produce?
+----------------------------------
+
+It produces XHTML compatible with the `HTML 4.01`_ and `XHTML 1.0`_
+specifications (within reason; there are some incompatibilities
+between the specs).  A cascading style sheet ("default.css" by
+default) is required for proper viewing with a modern graphical
+browser.  Correct rendering of the HTML produced depends on the CSS
+support of the browser.
+
+.. _HTML 4.01: http://www.w3.org/TR/html4/
+.. _XHTML 1.0: http://www.w3.org/TR/xhtml1/
+
+
+What browsers are supported?
+----------------------------
+
+No specific browser is targeted; all modern graphical browsers should
+work.  Some older browsers, text-only browsers, and browsers without
+full CSS support are known to produce inferior results.  Mozilla
+(version 1.0 and up) and MS Internet Explorer (version 5.0 and up) are
+known to give good results.  Reports of experiences with other
+browsers are welcome.
+
+
+Unexpected results from tools/rst2html.py: H1, H1 instead of H1, H2.  Why?
+--------------------------------------------------------------------------
+
+Here's the question in full:
+
+    I have this text::
+
+        Heading 1
+        =========
+
+        All my life, I wanted to be H1.
+
+        Heading 1.1
+        -----------
+
+        But along came H1, and so shouldn't I be H2?
+        No!  I'm H1!
+
+        Heading 1.1.1
+        *************
+
+        Yeah, imagine me, I'm stuck at H3!  No?!?
+
+    When I run it through tools/rst2html.py, I get unexpected results
+    (below).  I was expecting H1, H2, then H3; instead, I get H1, H1,
+    H2::
+
+        ...
+        <html lang="en">
+        <head>
+        ...
+        <title>Heading 1</title>
+        <link rel="stylesheet" href="default.css" type="text/css" />
+        </head>
+        <body>
+        <div class="document" id="heading-1">
+        <h1 class="title">Heading 1</h1>                <-- first H1
+        <p>All my life, I wanted to be H1.</p>
+        <div class="section" id="heading-1-1">
+        <h1><a name="heading-1-1">Heading 1.1</a></h1>        <-- H1
+        <p>But along came H1, and so now I must be H2.</p>
+        <div class="section" id="heading-1-1-1">
+        <h2><a name="heading-1-1-1">Heading 1.1.1</a></h2>
+        <p>Yeah, imagine me, I'm stuck at H3!</p>
+        ...
+
+    What gives?
+
+Check the "class" attribute on the H1 tags, and you will see a
+difference.  The first H1 is actually ``<h1 class="title">``; this is
+the document title, and the default stylesheet renders it centered.
+There can also be an ``<h2 class="subtitle">`` for the document
+subtitle.
+
+If there's only one highest-level section title at the beginning of a
+document, it is treated specially, as the document title.  (Similarly, a
+lone second-highest-level section title may become the document
+subtitle.)  See `How can I indicate the document title?  Subtitle?`_ for
+details.  Rather than use a plain H1 for the document title, we use ``<h1
+class="title">`` so that we can use H1 again within the document.  Why
+do we do this?  HTML only has H1-H6, so by making H1 do double duty, we
+effectively reserve these tags to provide 6 levels of heading beyond the
+single document title.
+
+HTML is being used for dumb formatting for nothing but final display.
+A stylesheet *is required*, and one is provided:
+tools/stylesheets/default.css.  Of course, you're welcome to roll your
+own.  The default stylesheet provides rules to format ``<h1
+class="title">`` and ``<h2 class="subtitle">`` differently from
+ordinary ``<h1>`` and ``<h2>``::
+
+    h1.title {
+      text-align: center }
+
+    h2.subtitle {
+      text-align: center }
+
+If you don't want the top section heading to be interpreted as a
+title at all, disable the `doctitle_xform`_ setting
+(``--no-doc-title`` option).  This will interpret your document
+differently from the standard settings, which might not be a good
+idea.  If you don't like the reuse of the H1 in the HTML output, you
+can tweak the `initial_header_level`_ setting
+(``--initial-header-level`` option) -- but unless you match its value
+to your specific document, you might end up with bad HTML (e.g. H3
+without H2).
+
+.. _doctitle_xform:
+   http://docutils.sourceforge.net/docs/user/config.html#doctitle-xform
+.. _initial_header_level:
+   http://docutils.sourceforge.net/docs/user/config.html#initial-header-level
+
+(Thanks to Mark McEahern for the question and much of the answer.)
+
+
+Why do enumerated lists only use numbers (no letters or roman numerals)?
+------------------------------------------------------------------------
+
+The rendering of enumerators (the numbers or letters acting as list
+markers) is completely governed by the stylesheet, so either the
+browser can't find the stylesheet (try using the "--embed-stylesheet"
+option), or the browser can't understand it (try a recent Firefox,
+Mozilla, Konqueror, Opera, Safari, or even MSIE).
+
+
+There appear to be garbage characters in the HTML.  What's up?
+--------------------------------------------------------------
+
+What you're seeing is most probably not garbage, but the result of a
+mismatch between the actual encoding of the HTML output and the
+encoding your browser is expecting.  Your browser is misinterpreting
+the HTML data, which is encoded text.  A discussion of text encodings
+is beyond the scope of this FAQ; see one or more of these documents
+for more info:
+
+* `UTF-8 and Unicode FAQ for Unix/Linux
+  <http://www.cl.cam.ac.uk/~mgk25/unicode.html>`_
+
+* Chapters 3 and 4 of `Introduction to i18n [Internationalization]
+  <http://www.debian.org/doc/manuals/intro-i18n/>`_
+
+* `Python Unicode Tutorial
+  <http://www.reportlab.com/i18n/python_unicode_tutorial.html>`_
+
+* `Python Unicode Objects: Some Observations on Working With Non-ASCII
+  Character Sets <http://effbot.org/zone/unicode-objects.htm>`_
+
+The common case is with the default output encoding (UTF-8), when
+either numbered sections are used (via the "sectnum_" directive) or
+symbol-footnotes.  3 non-breaking spaces are inserted in each numbered
+section title, between the generated number and the title text.  Most
+footnote symbols are not available in ASCII, nor are non-breaking
+spaces.  When encoded with UTF-8 and viewed with ordinary ASCII tools,
+these characters will appear to be multi-character garbage.
+
+You may have an decoding problem in your browser (or editor, etc.).
+The encoding of the output is set to "utf-8", but your browswer isn't
+recognizing that.  You can either try to fix your browser (enable
+"UTF-8 character set", sometimes called "Unicode"), or choose a
+different encoding for the HTML output.  You can also try
+``--output-encoding=ascii:xmlcharrefreplace`` for HTML (not applicable
+to non-XMLish outputs).
+
+If you're generating document fragments, the "Content-Type" metadata
+(between the HTML ``<head>`` and ``</head>`` tags) must agree with the
+encoding of the rest of the document.  For UTF-8, it should be::
+
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+Also, Docutils normally generates an XML declaration as the first line
+of the output.  It must also match the document encoding.  For UTF-8::
+
+    <?xml version="1.0" encoding="utf-8" ?>
+
+.. _sectnum:
+   http://docutils.sourceforge.net/docs/ref/rst/directives.html#sectnum
+
+
+Python Source Reader
+====================
+
+Can I use Docutils for Python auto-documentation?
+-------------------------------------------------
+
+Yes, in conjunction with other projects.
+
+Docstring extraction functionality from within Docutils is still under
+development.  There is most of a source code parsing module in
+docutils/readers/python/moduleparser.py.  We do plan to finish it
+eventually.  Ian Bicking wrote an initial front end for the
+moduleparser.py module, in sandbox/ianb/extractor/extractor.py.  Ian
+also did some work on the Python Source Reader
+(docutils.readers.python) component at PyCon DC 2004.
+
+Version 2.0 of Ed Loper's `Epydoc <http://epydoc.sourceforge.net/>`_
+supports reStructuredText-format docstrings for HTML output.  Docutils
+0.3 or newer is required.  Development of a Docutils-specific
+auto-documentation tool will continue.  Epydoc works by importing
+Python modules to be documented, whereas the Docutils-specific tool,
+described above, will parse modules without importing them (as with
+`HappyDoc <http://happydoc.sourceforge.net/>`_, which doesn't support
+reStructuredText).
+
+The advantages of parsing over importing are security and flexibility;
+the disadvantage is complexity/difficulty.
+
+* Security: untrusted code that shouldn't be executed can be parsed;
+  importing a module executes its top-level code.
+* Flexibility: comments and unofficial docstrings (those not supported
+  by Python syntax) can only be processed by parsing.
+* Complexity/difficulty: it's a lot harder to parse and analyze a
+  module than it is to ``import`` and analyze one.
+
+For more details, please see "Docstring Extraction Rules" in `PEP
+258`_, item 3 ("How").
+
+
+Miscellaneous
+=============
+
+Is the Docutils document model based on any existing XML models?
+----------------------------------------------------------------
+
+Not directly, no.  It borrows bits from DocBook, HTML, and others.  I
+(David Goodger) have designed several document models over the years,
+and have my own biases.  The Docutils document model is designed for
+simplicity and extensibility, and has been influenced by the needs of
+the reStructuredText markup.

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/HISTORY.txt
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/HISTORY.txt	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/HISTORY.txt	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,1720 @@
+==================
+ Docutils History
+==================
+
+:Author: David Goodger; open to all Docutils developers
+:Contact: goodger at python.org
+:Date: $Date: 2005/01/07 15:11:43 $
+:Web site: http://docutils.sourceforge.net/
+:Copyright: This document has been placed in the public domain.
+
+.. contents::
+
+
+Release 0.3.7 (2004-12-24)
+==========================
+
+* docutils/frontend.py:
+
+  - Added options: --input-encoding-error-handler,
+    --record-dependencies, --leave-footnote-reference-space,
+    --strict-visitor.
+  - Added command-line and config file support for "overrides" setting
+    parameter.
+
+* docutils/io.py:
+
+  - Added support for input encoding error handler.
+
+* docutils/nodes.py:
+
+  - Added dispatch_visit and dispatch_departure methods to
+    NodeVisitor; useful as a hook for Visitors.
+  - Changed structure of ``line_block``; added ``line``.
+  - Added ``compound`` node class.
+  - Added a mechanism for Visitors to transitionally ignore new node
+    classes.
+
+* docutils/utils.py:
+
+  - Moved ``escape2null`` and ``unescape`` functions from
+    docutils/parsers/rst/states.py.
+
+* docutils/parsers/rst/roles.py:
+
+  - Added "raw" role.
+  - Changed role function API: the "text" parameter now takes
+    null-escaped interpreted text content.
+
+* docutils/parsers/rst/states.py:
+
+  - Fixed bug where a "role" directive in a nested parse would crash
+    the parser; the state machine's "language" attribute was not being
+    copied over.
+  - Added support for line block syntax.
+  - Fixed directive parsing bug: argument-less directives didn't
+    notice that arguments were present.
+  - Removed error checking for transitions.
+  - Added support for multiple classifiers in definition list items.
+  - Moved ``escape2null`` and ``unescape`` functions to docutils/utils.py.
+  - Changed role function API: the "text" parameter now takes
+    null-escaped interpreted text content.
+  - Empty sections and documents are allowed now.
+
+* docutils/parsers/rst/directives/__init__.py:
+
+  - Added ``encoding`` directive option conversion function.
+  - Allow multiple class names in class_option conversion function.
+
+* docutils/parsers/rst/directives/body.py:
+
+  - Converted the line-block directive to use the new structure.
+  - Extracted the old line-block functionality to the ``block``
+    function (still used).
+  - Added ``compound`` directive (thanks to Felix Wiemann).
+
+* docutils/parsers/rst/directives/misc.py:
+
+  - Added "encoding" option to "include" and "raw" directives.
+  - Added "trim", "ltrim", and "rtrim" options to "unicode" directive.
+  - Allow multiple class names in the "class" directive.
+
+* docutils/parsers/rst/directives/parts.py:
+
+  - Directive "sectnum" now accepts "prefix", "suffix", and "start"
+    options.  Thanks to Lele Gaifax.
+
+* docutils/parsers/rst/directives/tables.py:
+
+  - Added "encoding" directive to "csv-table" directive.
+  - Added workaround for lack of Unicode support in csv.py, for
+    non-ASCII CSV input.
+
+* docutils/transforms/misc.py:
+
+  - Fixed bug when multiple "class" directives are applied to a single
+    element.
+  - Enabled multiple format names for "raw" directive.
+
+* docutils/transforms/references.py:
+
+  - Added support for trimming whitespace from beside substitution
+    references.
+
+* docutils/transforms/universal.py:
+
+  - FinalChecks now checks for illegal transitions and moves
+    transitions between sections.
+
+* docutils/writers/html4css1.py:
+
+  - HTMLTranslator.encode now converts U+00A0 to "&nbsp;".
+  - "stylesheet" and "stylesheet_path" settings are now mutually
+    exclusive.
+  - Added support for the new line_block/line structure.
+  - --footnote-references now overrides
+    --trim-footnote-reference-space, if applicable.
+  - Added support for ``compound`` elements.
+  - Enabled multiple format names for "raw" directive.
+  - ``<p>`` tags of a paragraph which is the only visible child of the
+    document node are no longer stripped.
+  - Moved paragraph-compacting logic (for stripping ``<p>`` tags) to
+    new method ``should_be_compact_paragraph()``.
+  - Added class="docutils" to ``dl``, ``hr``, ``table`` and ``tt``
+    elements.
+  - "raw" elements are now surrounded by ``span`` or ``div`` tags in
+    the output if they have their ``class`` attribute set.
+  - The whole document is now surrounded by a ``<div
+    class="document">`` element.
+  - Body-level images are now wrapped by their own ``<div>`` elements,
+    with image classes copied to the wrapper, and for images which
+    have the ``:align:`` option set, the surrounding ``<div>`` now
+    receives a class attribute (like ``class="align-left"``).
+
+* docutils/writers/latex2e.py:
+
+  - no newline after depart_term.
+  - Added translations for some Unicode quotes.
+  - Added option "font-encoding", made package AE the default.
+  - "stylesheet" and "stylesheet_path" settings are now mutually
+    exclusive.
+  - --footnote-references now overrides
+    --trim-footnote-reference-space, if applicable.
+  - The footnote label style now matches the footnote reference style
+    ("brackets" or "superscript").
+  - Added support for ``compound`` elements.
+  - Enabled multiple format names for "raw" directive.
+
+* docs/ref/docutils.dtd:
+
+  - Changed structure of the ``line_block`` element; added ``line``.
+  - Added ``compound`` element.
+  - Added "ltrim" and "rtrim" attributes to
+    ``substitution_definition`` element.
+  - Enabled multiple format names for ``raw`` element.
+  - Enabled multiple classifiers in ``definition_list_item`` elements.
+
+* docs/ref/rst/directives.txt
+
+  - Marked "line-block" as deprecated.
+  - "Class" directive now allows multiple class names.
+  - Added "Rationale for Class Attribute Value Conversion".
+  - Added warning about "raw" overuse/abuse.
+
+* docs/ref/rst/restructuredtext.txt:
+
+  - Added syntax for line blocks.
+  - Definition list items may have multiple classifiers.
+
+* docs/ref/rst/roles.txt:
+
+  - Added "raw" role.
+
+* tools/stylesheets/default.css:
+
+  - Added support for the new line_block structure.
+  - Added "docutils" class to ``dl``, ``hr``, ``table`` and ``tt``.
+
+
+Release 0.3.5 (2004-07-29)
+==========================
+
+General:
+
+* _`Documentation cleanup/reorganization`.
+
+  - Created new subdirectories of docs/:
+
+    * ``docs/user/``: introductory/tutorial material for end-users
+    * ``docs/dev/``: for core-developers (development notes, plans, etc.)
+    * ``docs/api/``: API reference material for client-developers
+    * ``docs/ref/``: reference material for all groups
+    * ``docs/howto/``: for component-developers and core-developers
+    * ``docs/peps/``: Python Enhancement Proposals
+
+  - Moved ``docs/*`` to ``docs/user/``.
+  - Moved ``pysource.dtd``, ``pysource.txt``, ``semantics.txt`` from
+    ``spec/`` to ``docs/dev``.
+  - Moved ``doctree.txt``, ``docutils.dtd``, ``soextblx.dtd``,
+    ``transforms.txt`` from ``spec/`` to ``docs/ref/``.
+  - Moved ``alternatives.txt``, and ``problems.txt`` from
+    ``spec/rst/`` to ``docs/dev/rst/``.
+  - Moved ``reStructuredText.txt``, ``directives.txt``,
+    ``interpreted.txt``, and ``introduction.txt`` from ``spec/rst/``
+    to ``docs/ref/rst/``.  Renamed ``interpreted.txt`` to
+    ``roles.txt``, ``reStructuredText.txt`` to
+    ``restructuredtext.txt``.
+  - Moved ``spec/howto/`` to ``docs/howto``.
+
+  In order to keep the CVS history of moved files, we supplied
+  SourceForge with a `script for modifying the Docutils CVS
+  repository`__.
+
+  __ http://cvs.sourceforge.net/viewcvs.py/*checkout*/docutils/sandbox/davidg/infrastructure/cvs-reorg.sh?content-type=text/plain&rev=1.5
+
+  After running the cleanup script:
+
+  - Added ``docs/index.txt``.
+  - Added a ``.htaccess`` file to the ``web`` module, containing
+    redirects for all old paths to new paths.  They'll preserve
+    fragments (the "#name" part of a URL), and won't clutter up the
+    file system, and will correct the URL in the user's browser.
+  - Added ``BUGS.txt``, ``docs/dev/policies.txt``,
+    ``docs/dev/website.txt``, ``docs/dev/release.txt`` from all but
+    the "To Do" list itself in ``docs/dev/todo.txt``.
+  - Moved "Future Plans" from ``HISTORY.txt`` to new "Priorities"
+    section of ``docs/dev/todo.txt``.
+  - Added ``THANKS.txt`` from "Acknowledgements" in ``HISTORY.txt``.
+  - Added "How To Report Bugs" to ``BUGS.txt``.
+  - Went through all the sources and docs (including under web/) and
+    updated links.  Mostly done by Felix Wiemann; thanks Felix!
+    (Still need to update links in the sandboxes.)
+
+Specific:
+
+* BUGS.txt: Added to project.
+
+* THANKS.txt: Added to project.
+
+* docutils/__init__.py:
+
+  - 0.3.4: Post-release.
+
+* docutils/core.py:
+
+  - Added special error handling & advice for UnicodeEncodeError.
+  - Refactored Publisher.publish (simplified exception handling &
+    extracted debug dumps).
+  - Renamed "enable_exit" parameter of convenience functions to
+    "enable_exit_status".
+  - Enabled traceback (exception propagation) by default in
+    programmatic convenience functions.
+  - Now publish_file and publish_cmdline convenience functions return
+    the encoded string results in addition to their regular I/O.
+  - Extracted common code from publish_file, publish_string, and
+    publish_parts, into new publish_programmatically.  Extracted
+    settings code to ``Publisher.process_programmatic_settings``.
+  - In Publisher.publish, disabled ``settings_overrides`` when
+    ``settings`` is supplied; redundant.
+
+* docutils/frontend.py:
+
+  - Added help text for "--output-encoding-error-handler" and
+    "--error-encoding-error-handler".
+  - Renamed "--exit" to "--exit-status".
+  - Simplified default-setting code.
+
+* docutils/parsers/rst/__init__.py:
+
+  - Added "--pep-base-url" and "--rfc-base-url" options.
+
+* docutils/parsers/rst/states.py:
+
+  - Made URI recognition more aggressive and intelligent.
+
+* docutils/parsers/rst/directives/__init__.py:
+
+  - Added several directive option conversion functions.
+
+* docutils/parsers/rst/directives/body.py:
+
+  - Moved "table" directive to tables.py.
+
+* docutils/parsers/rst/directives/tables.py: Table-related directives,
+  added to project.
+
+* docutils/writers/latex2e.py:
+
+  - Added "--table-style=(standard|booktabs|nolines)"
+  - figures get "here" option (LaTeX per default puts them at bottom),
+    and figure content is centered.
+  - Rowspan support for tables.
+  - Fix: admonition titles before first section.
+  - Replace ``--`` in literal by ``-{}-`` because fontencoding T1 has endash.
+  - Replave ``_`` in literal by an underlined blank, because it has the correct
+    width.
+  - Fix: encode pdfbookmark titles, ``#`` broke pdflatex.
+  - A few unicode replacements, if output_encoding != utf
+  - Add "--graphicx-option".
+  - Indent literal-blocks.
+  - Fix: omit ``\maketitle`` when there is no document title.
+
+* docs/index.txt: "Docutils Project Documentation Overview", added to
+  project.
+
+* docs/api/cmdline-tool.txt: "Inside A Docutils Command-Line Front-End
+  Tool", added to project.
+
+* docs/api/publisher.txt: "The Docutils Publisher", added to project.
+
+* docs/api/runtime-settings.txt: "Docutils Runtime Settings", added to project.
+
+* docs/dev/policies.txt: Added to project (extracted from
+  ``docs/dev/todo.txt``, formerly ``spec/notes.txt``).
+
+* docs/dev/release.txt: Added to project (extracted from
+  ``docs/dev/todo.txt``, formerly ``spec/notes.txt``).
+
+* docs/dev/testing.txt: Added to project.
+
+* docs/dev/website.txt: Added to project (extracted from
+  ``docs/dev/todo.txt``, formerly ``spec/notes.txt``).
+
+* docs/ref/rst/directives.txt:
+
+  - Added directives: "table", "csv-table".
+
+* docs/user/rst/cheatsheet.txt: "The reStructuredText Cheat Sheet"
+  added to project.  1 page for syntax, and a 1 page reference for
+  directives and roles.  Source text to be used as-is; not meant to be
+  converted to HTML.
+
+* docs/user/rst/demo.txt: Added to project; moved from tools/test.txt
+  with a change of title.
+
+* test/functional/, contents, and test/test_functional.py: Added to
+  project.
+
+* tools/buildhtml.py: Fixed bug with config file handling.
+
+* tools/html.py: Removed from project (duplicate of rst2html.py).
+
+* tools/pep2html.py: Removed from project (duplicate of Python's
+  nondist/peps/pep2html.py; Docutils' tools/pep.py can be used for
+  Docutils-related PEPs in docs/peps/).
+
+* tools/rst2pseudoxml.py: Renamed from publish.py.
+
+* tools/rst2xml.py: Renamed from docutils-xml.py.
+
+* tools/test.txt: Removed from project; moved to
+  docs/user/rst/demo.txt.
+
+* setup.py: Now also installs ``rst2latex.py``.
+
+
+Release 0.3.3 (2004-05-09)
+==========================
+
+* docutils/__init__.py:
+
+  - 0.3.1: Reorganized config file format (multiple sections); see
+    docs/config.txt.
+  - Added unknown_reference_resolvers attribute to TransformSpec.
+  - 0.3.2: Interpreted text reorganization.
+  - 0.3.3: Released.
+
+* docutils/core.py:
+
+  - Catch system messages to stop tracebacks from parsing errors.
+  - Catch exceptions during processing report & exit without
+    tracebacks, except when "--traceback" used.
+  - Reordered components for OptionParser; application comes last.
+  - Added "config_section" parameter to several methods and functions,
+    allowing front ends to easily specify their config file sections.
+  - Added publish_parts convenience function to allow access to individual
+    parts of a document.
+
+* docutils/examples.py: Added to project; practical examples of
+  Docutils client code, to be used as-is or as models for variations.
+
+* docutils/frontend.py:
+
+  - Added "--traceback" & "--no-traceback" options ("traceback"
+    setting).
+  - Implemented support for config file reorganization:
+    ``standard_config_files`` moved from ``ConfigParser`` to
+    ``OptionParser``; added
+    ``OptionParser.get_config_file_settings()`` and
+    ``.get_standard_config_settings()``; support for old "[options]"
+    section (with deprecation warning) and mapping from old to new
+    settings.
+  - Reimplemented setting validation.
+  - Enabled flexible boolean values: yes/no, true/false, on/off.
+  - Added ``Values``, a subclass of ``optparse.Values``, with support
+    for list setting attributes.
+  - Added support for new ``DOCUTILSCONFIG`` environment variable;
+    thanks to Beni Cherniavsky.
+  - Added "--no-section-numbering" option.
+
+* docutils/io.py:
+
+  - Catch IOErrors when opening source & destination files, report &
+    exit without tracebacks.  Added ``handle_io_errors`` parameter to
+    ``FileInput`` & ``FileOutput`` to enable caller error handling.
+
+* docutils/nodes.py:
+
+  - Changed ``SparseNodeVisitor`` and ``GenericNodeVisitor`` dynamic
+    method definitions (via ``exec``) to dynamic assignments (via
+    ``setattr``); thanks to Roman Suzi.
+  - Encapsulated visitor dynamic assignments in a function; thanks to
+    Ian Bicking.
+  - Added indirect_reference_name attribute to the Targetable
+    class. This attribute holds the whitespace_normalized_name
+    (contains mixed case) of a target.
+
+* docutils/statemachine.py:
+
+  - Renamed ``StringList.strip_indent`` to ``.trim_left``.
+  - Added ``StringList.get_2D_block``.
+
+* docutils/utils.py:
+
+  - Added "level" attribute to SystemMessage exceptions.
+
+* docutils/languages/af.py: Added to project; Afrikaans mappings by
+  Jannie Hofmeyr.
+
+* docutils/languages/cs.py: Added to project; Czech mappings by Marek
+  Blaha.
+
+* docutils/languages/eo.py: Added to project; Esperanto mappings by
+  Marcelo Huerta San Martin.
+
+* docutils/languages/pt_br.py: Added to project; Brazilian Portuguese
+  mappings by Lalo Martins.
+
+* docutils/languages/ru.py: Added to project; Russian mappings by
+  Roman Suzi.
+
+* docutils/parsers/rst/roles.py: Added to project.  Contains
+  interpreted text role functions, a registry for interpreted text
+  roles, and an API for adding to and retrieving from the registry.
+  Contributed by Edward Loper.
+
+* docutils/parsers/rst/states.py:
+
+  - Updated ``RSTState.nested_parse`` for "include" in table cells.
+  - Allowed true em-dash character and "---" as block quote
+    attribution marker.
+  - Added support for <angle-bracketed> complex option arguments
+    (option lists).
+  - Fixed handling of backslashes in substitution definitions.
+  - Fixed off-by-1 error with extra whitespace after substitution
+    definition directive.
+  - Added inline markup parsing to field lists' field names.
+  - Added support for quoted (and unindented) literal blocks.
+    Driven in part by a bribe from Frank Siebenlist (thanks!).
+  - Parser now handles escapes in URIs correctly.
+  - Made embedded-URIs' reference text omittable.  Idea from Beni
+    Cherniavsky.
+  - Refactored explicit target processing code.
+  - Added name attribute to references containing the reference name only
+    through whitespace_normalize_name (no case changes).
+  - parse_target no longer returns the refname after going through
+    normalize_name. This is now handled in make_target.
+  - Fixed bug relating to role-less interpreted text in non-English
+    contexts.
+  - Reorganized interpreted text processing; moved code into the new
+    roles.py module.  Contributed by Edward Loper.
+  - Refactored ``Body.parse_directive`` into ``run_directive`` and
+    ``parse_directive_block``.
+
+* docutils/parsers/rst/tableparser.py:
+
+  - Reworked for ``StringList``, to support "include" directives in
+    table cells.
+
+* docutils/parsers/rst/directives/__init__.py:
+
+  - Renamed ``unchanged()`` directive option conversion function to
+    ``unchanged_required``, and added a new ``unchanged``.
+  - Catch unicode value too high error; fixes bug 781766.
+  - Beefed up directive error reporting.
+
+* docutils/parsers/rst/directives/body.py:
+
+  - Added basic "table" directive.
+
+* docutils/parsers/rst/directives/images.py:
+
+  - Added "target" option to "image" directive.
+  - Added name attribute to references containing the reference name only
+    through whitespace_normalize_name (no case changes).
+
+* docutils/parsers/rst/directives/misc.py:
+
+  - Isolated the import of the ``urllib2`` module; was causing
+    problems on SourceForge (``libssl.so.2`` unavailable?).
+  - Added the "role" directive for declaring custom interpreted text
+    roles.
+
+* docutils/parsers/rst/directives/parts.py:
+
+  - The "contents" directive does more work up-front, creating the
+    "topic" and "title", and leaving the "pending" node for the
+    transform.  Allows earlier reference resolution; fixes subtle bug.
+
+* docutils/parsers/rst/languages/af.py: Added to project; Afrikaans
+  mappings by Jannie Hofmeyr.
+
+* docutils/parsers/rst/languages/cs.py: Added to project; Czech
+  mappings by Marek Blaha.
+
+* docutils/parsers/rst/languages/eo.py: Added to project; Esperanto
+  mappings by Marcelo Huerta San Martin.
+
+* docutils/parsers/rst/languages/pt_br.py: Added to project; Brazilian
+  Portuguese mappings by Lalo Martins.
+
+* docutils/parsers/rst/languages/ru.py: Added to project; Russian
+  mappings by Roman Suzi.
+
+* docutils/transforms/parts.py:
+
+  - The "contents" directive does more work up-front, creating the
+    "topic" and "title", and leaving the "pending" node for the
+    transform.  Allows earlier reference resolution; fixes subtle bug.
+  - Added support for disabling of section numbering.
+    
+* docutils/transforms/references.py:
+
+  - Verifying that external targets are truly targets and not indirect 
+    references. This is because we are now adding a "name" attribute to 
+    references in addition to targets. Note sure if this is correct!
+  - Added code to hook into the unknown_reference_resolvers list for a
+    transformer in resolve_indirect_target. This allows the
+    unknown_reference_resolvers to keep around indirect targets which
+    docutils doesn't know about.
+  - Added specific error message for duplicate targets.
+
+* docutils/transforms/universal.py:
+
+  - Added FilterMessages transform (removes system messages below the
+    verbosity threshold).
+  - Added hook (via docutils.TransformSpec.unknown_reference_resolvers)
+    to FinalCheckVisitor for application-specific handling of 
+    unresolvable references.
+  - Added specific error message for duplicate targets.
+  
+* docutils/writers/__init__.py:
+
+  - Added assemble_parts method to the Writer class to allow for
+    access to a documents individual parts.
+  - Documented & set default for ``Writer.output`` attribute.
+
+* docutils/writers/html4css1.py:
+
+  - Fixed unicode handling of attribute values (bug 760673).
+  - Prevent duplication of "class" attribute values (bug report from
+    Kirill Lapshin).
+  - Improved table grid/border handling (prompted by report from Bob
+    Marshall).
+  - Added support for table titles.
+  - Added "<title />" for untitled docs, for XHTML conformance; thanks
+    to Darek Suchojad.
+  - Added functionality to keep track of individual parts of a document
+    and store them in a dictionary as the "parts" attribute of the writer.
+    Contributed by Reggie Dugard at the Docutils sprint at PyCon DC 2004.
+  - Added proper support for the "scale" attribute of the "image"
+    element.  Contributed by Brent Cook.
+  - Added ``--initial-header-level`` option.
+  - Fixed bug: the body_pre_docinfo segment depended on there being a
+    docinfo; if no docinfo, the document title was incorporated into
+    the body segment.  Adversely affected the publish_parts interface.
+
+* docutils/writers/latex2e.py:
+
+  - Changed default stylesheet to "no stylesheet" to avoid latex complaining
+    about a missing file.
+  - Added options and support: ``--compound-enumerators``,
+    ``--section-prefix-for-enumerators``, and
+    ``--section-enumerator-separator``.  By John F Meinel Jr (SF patch
+    934322).
+  - Added option ``--use-verbatim-when-possible``, to avoid
+    problematic characters (eg, '~' in italian) in literal blocks.
+  - It's now possible to use four section levels in the `book` and
+    `report` LaTeX classes.  The default `article` class still has
+    three levels limit.
+    
+* docs/config.txt: "Docutils Configuration Files", added to project.
+  Moved config file entry descriptions from tools.txt.
+
+* docs/tools.txt:
+
+  - Moved config file entry descriptions to config.txt.
+
+* spec/notes.txt: Continual updates.  Added "Setting Up For Docutils
+  Development".
+
+* spec/howto/rst-roles.txt: "Creating reStructuredText Interpreted
+  Text Roles", added to project.
+
+* spec/rst/reStructuredText.txt:
+
+  - Added description of support for <angle-bracketed> complex option
+    arguments to option lists.
+  - Added subsections for indented and quoted literal blocks.
+
+* test: Continually adding & updating tests.
+
+  - Added test/test_settings.py & test/data/config_*.txt support
+    files.
+  - Added test/test_writers/test_htmlfragment.py.
+
+* test/DocutilsTestSupport.py:
+
+  - Refactored LaTeX publisher test suite/case class names to make
+    testing other writers easier.
+  - Added HtmlWriterPublishTestCase and HtmlFragmentTestSuite classes
+    to test the processing of HTML fragments which use the new
+    publish_parts convenience function.
+
+* tools/buildhtml.py:
+
+  - Added support for the "--prune" option.
+  - Removed dependency on pep2html.py; plaintext PEPs no longer
+    supported.
+
+* tools/docutils.conf:
+
+  - Updated for configuration file reorganization.
+
+* tools/rst2html.py:
+
+  - copied from tools/html.py
+
+* setup.py:
+
+  - added a 'scripts' section to configuration
+  - added 'tools/rst2html.py' to the scripts section
+
+
+Release 0.3 (2003-06-24)
+========================
+
+General:
+
+* Renamed "attribute" to "option" for directives/extensions.
+
+* Renamed transform method "transform" to "apply".
+
+* Renamed "options" to "settings" for runtime settings (as set by
+  command-line options).  Sometimes "option" (singular) became
+  "settings" (plural).  Some variations below:
+
+  - document.options -> document.settings (stored in other objects as
+    well)
+  - option_spec -> settings_spec (not directives though)
+  - OptionSpec -> SettingsSpec
+  - cmdline_options -> settings_spec
+  - relative_path_options -> relative_path_settings
+  - option_default_overrides -> settings_default_overrides
+  - Publisher.set_options -> Publisher.get_settings
+
+Specific:
+
+* COPYING.txt: Added "Public Domain Dedication".
+
+* FAQ.txt: Frequently asked questions, added to project.
+
+* setup.py:
+
+  - Updated with PyPI Trove classifiers.
+  - Conditional installation of third-party modules.
+
+* docutils/__init__.py:
+
+  - Bumped version to 0.2.1 to reflect changes to I/O classes.
+  - Bumped version to 0.2.2 to reflect changes to stylesheet options.
+  - Factored ``SettingsSpec`` out of ``Component``; separately useful.
+  - Bumped version to 0.2.3 because of the new "--embed-stylesheet"
+    option and its effect on the PEP template & writer.
+  - Bumped version to 0.2.4 due to changes to the PEP template &
+    stylesheet.
+  - Bumped version to 0.2.5 to reflect changes to Reporter output.
+  - Added ``TransformSpec`` class for new transform system.
+  - Bumped version to 0.2.6 for API changes (renaming).
+  - Bumped version to 0.2.7 for new ``docutils.core.publish_*``
+    convenience functions.
+  - Added ``Component.component_type`` attribute.
+  - Bumped version to 0.2.8 because of the internal parser switch from
+    plain lists to the docutils.statemachine.StringList objects.
+  - Bumped version to 0.2.9 because of the frontend.py API changes.
+  - Bumped version to 0.2.10 due to changes to the project layout
+    (third-party modules removed from the "docutils" package), and
+    signature changes in ``io.Input``/``io.Output``.
+  - Changed version to 0.3.0 for release.
+
+* docutils/core.py:
+
+  - Made ``publish()`` a bit more convenient.
+  - Generalized ``Publisher.set_io``.
+  - Renamed ``publish()`` to ``publish_cmdline()``; rearranged its
+    parameters; improved its docstring.
+  - Added ``publish_file()`` and ``publish_string()``.
+  - Factored ``Publisher.set_source()`` and ``.set_destination()``
+    out of ``.set_io``.
+  - Added support for "--dump-pseudo-xml", "--dump-settings", and
+    "--dump-transforms" hidden options.
+  - Added ``Publisher.apply_transforms()`` method.
+  - Added ``Publisher.set_components()`` method; support for
+    ``publish_*()`` conveninece functions.
+  - Moved config file processing to docutils/frontend.py.
+  - Added support for exit status ("exit_level" setting &
+    ``enable_exit`` parameter for Publisher.publish() and convenience
+    functions).
+
+* docutils/frontend.py:
+
+  - Check for & exit on identical source & destination paths.
+  - Fixed bug with absolute paths & "--config".
+  - Set non-command-line defaults in ``OptionParser.__init__()``:
+    ``_source`` & ``_destination``.
+  - Distributed ``relative_path_settings`` to components; updated
+    ``OptionParser.populate_from_components()`` to combine it all.
+  - Require list of keys in ``make_paths_absolute`` (was implicit in
+    global ``relative_path_settings``).
+  - Added "--expose-internal-attribute", "--dump-pseudo-xml",
+    "--dump-settings", and "--dump-transforms" hidden options.
+  - Removed nasty internals-fiddling ``ConfigParser.get_section``
+    code, replaced with correct code.
+  - Added validation functionality for config files.
+  - Added "--error-encoding" option/setting, "_disable_config"
+    internal setting.
+  - Added encoding validation; updated "--input-encoding" and
+    "--output-encoding"; added "--error-encoding-error-handler" and
+    "--output-encoding-error-handler".
+  - Moved config file processing from docutils/core.py.
+  - Updated ``OptionParser.populate_from_components`` to handle new
+    ``SettingsSpec.settings_defaults`` dict.
+  - Added support for "-" => stdin/stdout.
+  - Added "exit_level" setting ("--exit" option).
+
+* docutils/io.py:
+
+  - Split ``IO`` classes into subclasses of ``Input`` and ``Output``.
+  - Added automatic closing to ``FileInput`` and ``FileOutput``.
+  - Delayed opening of ``FileOutput`` file until ``write()`` called.
+  - ``FileOutput.write()`` now returns the encoded output string.
+  - Try to get path/stream name automatically in ``FileInput`` &
+    ``FileOutput``.
+  - Added defaults for source & destination paths.
+  - Allow for Unicode I/O with an explicit "unicode" encoding.
+  - Added ``Output.encode()``.
+  - Removed dependency on runtime settings; pass encoding directly.
+  - Recognize Unicode strings in ``Input.decode()``.
+  - Added support for output encoding error handlers.
+
+* docutils/nodes.py:
+
+  - Added "Invisible" element category class.
+  - Changed ``Node.walk()`` & ``.walkabout()`` to permit more tree
+    modification during a traversal.
+  - Added element classes: ``line_block``, ``generated``, ``address``,
+    ``sidebar``, ``rubric``, ``attribution``, ``admonition``,
+    ``superscript``, ``subscript``, ``inline``
+  - Added support for lists of nodes to ``Element.insert()``.
+  - Fixed parent linking in ``Element.replace()``.
+  - Added new abstract superclass ``FixedTextElement``; adds
+    "xml:space" attribute.
+  - Added support for "line" attribute of ``system_message`` nodes.
+  - Added support for the observer pattern from ``utils.Reporter``.
+    Added ``parse_messages`` and ``transform_messages`` attributes to
+    ``document``, removed ``messages``.  Added ``note_parse_message``
+    and ``note_transform_message`` methods.
+  - Added support for improved diagnostics:
+
+    - Added "document", "source", and "line" internal attributes to
+      ``Node``, set by ``Node.setup_child()``.
+    - Converted variations on ``node.parent = self`` to
+      ``self.setup_child(node)``.
+    - Added ``document.current_source`` & ``.current_line``
+      attributes, and ``.note_source`` observer method.
+    - Changed "system_message" output to GNU-Tools format.
+
+  - Added a "rawsource" attribute to the ``Text`` class, for text
+    before backslash-escape resolution.
+  - Support for new transform system.
+  - Reworked ``pending`` element.
+  - Fixed XML DOM bug (SF #660611).
+  - Removed the ``interpeted`` element class and added
+    ``title_reference``, ``abbreviation``, ``acronym``.
+  - Made substitutions case-sensitive-but-forgiving; moved some code
+    from the parser.
+  - Fixed Unicode bug on element attributes (report: William Dode).
+
+* docutils/optik.py: Removed from project; replaced with
+  extras/optparse.py and extras/textwrap.py.  These will be installed
+  only if they're not already present in the Python installation.
+
+* docutils/roman.py: Moved to extras/roman.py; this will be installed
+  only if it's not already present in the Python installation.
+
+* docutils/statemachine.py:
+
+  - Factored out ``State.add_initial_transitions()`` so it can be
+    extended.
+  - Converted whitespace-specific "blank" and "indent" transitions
+    from special-case code to ordinary transitions: removed
+    ``StateMachineWS.check_line()`` & ``.check_whitespace()``, added
+    ``StateWS.add_initial_transitions()`` method, ``ws_patterns`` &
+    ``ws_initial_transitions`` attributes.
+  - Removed ``State.match_transition()`` after merging it into
+    ``.check_line()``.
+  - Added ``StateCorrection`` exception.
+  - Added support for ``StateCorrection`` in ``StateMachine.run()``
+    (moved ``TransitionCorrection`` support there too.)
+  - Changed ``StateMachine.next_line()`` and ``.goto_line()`` to raise
+    ``EOFError`` instead of ``IndexError``.
+  - Added ``State.no_match`` method.
+  - Added support for the Observer pattern, triggered by input line
+    changes.
+  - Added ``strip_top`` parameter to
+    ``StateMachineWS.get_first_known_indented``.
+  - Made ``context`` a parameter to ``StateMachine.run()``.
+  - Added ``ViewList`` & ``StringList`` classes;
+    ``extract_indented()`` becomes ``StringList.get_indented()``.
+  - Added ``StateMachine.insert_input()``.
+  - Fixed ViewList slice handling for Python 2.3.  Patch from (and
+    thanks to) Fred Drake.
+
+* docutils/utils.py:
+
+  - Added a ``source`` attribute to Reporter instances and
+    ``system_message`` elements.
+  - Added an observer pattern to ``utils.Reporter`` to keep track of
+    system messages.
+  - Fixed bugs in ``relative_path()``.
+  - Added support for improved diagnostics.
+  - Moved ``normalize_name()`` to nodes.py (``fully_normalize_name``).
+  - Added support for encoding Reporter stderr output, and encoding
+    error handlers.
+  - Reporter keeps track of the highest level system message yet
+    generated.
+
+* docutils/languages: Fixed bibliographic field language lookups.
+
+* docutils/languages/es.py: Added to project; Spanish mappings by
+  Marcelo Huerta San Martin.
+
+* docutils/languages/fr.py: Added to project; French mappings by
+  Stefane Fermigier.
+
+* docutils/languages/it.py: Added to project; Italian mappings by
+  Nicola Larosa.
+
+* docutils/languages/sk.py: Added to project; Slovak mappings by
+  Miroslav Vasko.
+
+* docutils/parser/__init__.py:
+
+  - Added ``Parser.finish_parse()`` method.
+
+* docutils/parser/rst/__init__.py:
+
+  - Added options: "--pep-references", "--rfc-references",
+    "--tab-width", "--trim-footnote-reference-space".
+
+* docutils/parsers/rst/states.py:
+
+  - Changed "title under/overline too short" system messages from INFO
+    to WARNING, and fixed its insertion location.
+  - Fixed enumerated list item parsing to allow paragraphs & section
+    titles to begin with enumerators.
+  - Converted system messages to use the new "line" attribute.
+  - Fixed a substitution reference edge case.
+  - Added support for "--pep-references" and "--rfc-references"
+    options; reworked ``Inliner`` code to make customization easier.
+  - Removed field argument parsing.
+  - Added support for short section title over/underlines.
+  - Fixed "simple reference name" regexp to ignore text like
+    "object.__method__"; not an anonymous reference.
+  - Added support for improved diagnostics.
+  - Reworked directive API, based on Dethe Elza's contribution.  Added
+    ``Body.parse_directive()``, ``.parse_directive_options()``,
+    ``.parse_directive_arguments()`` methods.
+  - Added ``ExtensionOptions`` class, to parse directive options
+    without parsing field bodies.  Factored
+    ``Body.parse_field_body()`` out of ``Body.field()``, overridden in
+    ``ExtensionOptions``.
+  - Improved definition list term/classifier parsing.
+  - Added warnings for unknown directives.
+  - Renamed ``Stuff`` to ``Struct``.
+  - Now flagged as errors: transitions at the beginning or end of
+    sections, empty sections (except title), and empty documents.
+  - Updated for ``statemachine.StringList``.
+  - Enabled recognition of schemeless email addresses in targets.
+  - Added support for embedded URIs in hyperlink references.
+  - Added backslash-escapes to inline markup end-string suffix.
+  - Added support for correct interpreted text processing.
+  - Fixed nested title parsing (topic, sidebar directives).
+  - Added special processing of backslash-escaped whitespace (idea
+    from David Abrahams).
+  - Made substitutions case-sensitive-but-forgiving; moved some code
+    to ``docutils.nodes``.
+  - Added support for block quote attributions.
+  - Added a kludge to work-around a conflict between the bubble-up
+    parser strategy and short titles (<= 3 char-long over- &
+    underlines).  Fixes SF bug #738803 "infinite loop with multiple
+    titles" submitted by Jason Diamond.
+  - Added explicit interpreted text roles for standard inline markup:
+    "emphasis", "strong", "literal".
+  - Implemented "superscript" and "subscript" interpreted text roles.
+  - Added initial support for "abbreviation" and "acronym" roles;
+    incomplete.
+  - Added support for "--trim-footnote-reference-space" option.
+  - Optional space before colons in directives & hyperlink targets.
+
+* docutils/parsers/rst/tableparser.py:
+
+  - Fixed a bug that was producing unwanted empty rows in "simple"
+    tables.
+  - Detect bad column spans in "simple" tables.
+
+* docutils/parsers/rst/directives: Updated all directive functions to
+  new API.
+
+* docutils/parsers/rst/directives/__init__.py:
+
+  - Added ``flag()``, ``unchanged()``, ``path()``,
+    ``nonnegative_int()``, ``choice()``, and ``class_option()``
+    directive option helper functions.
+  - Added warnings for unknown directives.
+  - Return ``None`` for missing directives.
+  - Added ``register_directive()``, thanks to William Dode and Paul
+    Moore.
+
+* docutils/parsers/rst/directives/admonitions.py:
+
+  - Added "admonition" directive.
+
+* docutils/parsers/rst/directives/body.py: Added to project.  Contains
+  the "topic", "sidebar" (from Patrick O'Brien), "line-block",
+  "parsed-literal", "rubric", "epigraph", "highlights" and
+  "pull-quote" directives.
+
+* docutils/parsers/rst/directives/images.py:
+
+  - Added an "align" attribute to the "image" & "figure" directives
+    (by Adam Chodorowski).
+  - Added "class" option to "image", and "figclass" to "figure".
+
+* docutils/parsers/rst/directives/misc.py:
+
+  - Added "include", "raw", and "replace" directives, courtesy of
+    Dethe Elza.
+  - Added "unicode" and "class" directives.
+
+* docutils/parsers/rst/directives/parts.py:
+
+  - Added the "sectnum" directive; by Dmitry Jemerov.
+  - Added "class" option to "contents" directive.
+
+* docutils/parsers/rst/directives/references.py: Added to project.
+  Contains the "target-notes" directive.
+
+* docutils/parsers/rst/languages/__init__.py:
+
+  - Return ``None`` from get_language() for missing language modules.
+
+* docutils/parsers/rst/languages/de.py: Added to project; German
+  mappings by Engelbert Gruber.
+
+* docutils/parsers/rst/languages/en.py:
+
+  - Added interpreted text roles mapping.
+
+* docutils/parsers/rst/languages/es.py: Added to project; Spanish
+  mappings by Marcelo Huerta San Martin.
+
+* docutils/parsers/rst/languages/fr.py: Added to project; French
+  mappings by William Dode.
+
+* docutils/parsers/rst/languages/it.py: Added to project; Italian
+  mappings by Nicola Larosa.
+
+* docutils/parsers/rst/languages/sk.py: Added to project; Slovak
+  mappings by Miroslav Vasko.
+
+* docutils/readers/__init__.py:
+
+  - Added support for the observer pattern from ``utils.Reporter``, in
+    ``Reader.parse`` and ``Reader.transform``.
+  - Removed ``Reader.transform()`` method.
+  - Added default parameter values to ``Reader.__init__()`` to make
+    instantiation easier.
+  - Removed bogus aliases: "restructuredtext" is *not* a Reader.
+
+* docutils/readers/pep.py:
+
+  - Added the ``peps.TargetNotes`` transform to the Reader.
+  - Removed PEP & RFC reference detection code; moved to
+    parsers/rst/states.py as options (enabled here by default).
+  - Added support for pre-acceptance PEPs (no PEP number yet).
+  - Moved ``Inliner`` & made it a class attribute of ``Reader`` for
+    easy subclassing.
+
+* docutils/readers/python: Python Source Reader subpackage added to
+  project, including preliminary versions of:
+
+  - __init__.py
+  - moduleparser.py: Parser for Python modules.
+
+* docutils/transforms/__init__.py:
+
+  - Added ``Transformer`` class and completed transform reform.
+  - Added unknown_reference_resolvers list for each transformer. This list holds
+    the list of functions provided by each component of the transformer that
+    help resolve references.
+
+* docutils/transforms/frontmatter.py:
+
+  - Improved support for generic fields.
+  - Fixed bibliographic field language lookups.
+
+* docutils/transforms/misc.py: Added to project.  Miscellaneous
+  transforms.
+
+* docutils/transforms/parts.py:
+
+  - Moved the "id" attribute from TOC list items to the references
+    (``Contents.build_contents()``).
+  - Added the ``SectNum`` transform; by Dmitry Jemerov.
+  - Added "class" attribute support to ``Contents``.
+
+* docutils/transforms/peps.py:
+
+  - Added ``mask_email()`` function, updating to pep2html.py's
+    functionality.
+  - Linked "Content-Type: text/x-rst" to PEP 12.
+  - Added the ``TargetNotes`` PEP-specific transform.
+  - Added ``TargetNotes.cleanup_callback``.
+  - Added title check to ``Headers``.
+
+* docutils/transforms/references.py:
+
+  - Added the ``TargetNotes`` generic transform.
+  - Split ``Hyperlinks`` into multiple transforms.
+  - Fixed bug with multiply-indirect references (report: Bruce Smith).
+  - Added check for circular indirect references.
+  - Made substitutions case-sensitive-but-forgiving.
+
+* docutils/transforms/universal.py:
+
+  - Added support for the "--expose-internal-attributes" option.
+  - Removed ``Pending`` transform classes & data.
+
+* docutils/writers/__init__.py:
+
+  - Removed ``Writer.transform()`` method.
+
+* docutils/writers/docutils-xml.py:
+
+  - Added XML and doctype declarations.
+  - Added "--no-doctype" and "--no-xml-declaration" options.
+
+* docutils/writers/html4css1.py:
+
+  - "name" attributes only on these tags: a, applet, form, frame,
+    iframe, img, map.
+  - Added "name" attribute to <a> in section titles for Netscape 4
+    support (bug report: Pearu Peterson).
+  - Fixed targets (names) on footnote, citation, topic title,
+    problematic, and system_message nodes (for Netscape 4).
+  - Changed field names from "<td>" to "<th>".
+  - Added "@" to "&#64;" encoding to thwart address harvesters.
+  - Improved the vertical whitespace optimization; ignore "invisible"
+    nodes (targets, comments, etc.).
+  - Improved inline literals with ``<span class="pre">`` around chunks
+    of text and ``&nbsp;`` for runs of spaces.
+  - Improved modularity of output; added ``self.body_pre_docinfo`` and
+    ``self.docinfo`` segments.
+  - Added support for "line_block", "address" elements.
+  - Improved backlinks (footnotes & system_messages).
+  - Improved system_message output.
+  - Redefined "--stylesheet" as containing an invariant URL, used
+    verbatim.  Added "--stylesheet-path", interpreted w.r.t. the
+    working directory.
+  - Added "--footnote-references" option (superscript or brackets).
+  - Added "--compact-lists" and "--no-compact-lists" options.
+  - Added "--embed-stylesheet" and "--link-stylesheet" options;
+    factored out ``HTMLTranslator.get_stylesheet_reference()``.
+  - Improved field list rendering.
+  - Added Docutils version to "generator" meta tag.
+  - Fixed a bug with images; they must be inline, so wrapped in <p>.
+  - Improved layout of <pre> HTML source.
+  - Fixed attribute typo on <colspec>.
+  - Refined XML prologue.
+  - Support for no stylesheet.
+  - Removed "interpreted" element support.
+  - Added support for "title_reference", "sidebar", "attribution",
+    "rubric", and generic "admonition" elements.
+  - Added "--attribution" option.
+  - Added support for "inline", "subscript", "superscript" elements.
+  - Added initial support for "abbreviation" and "acronym";
+    incomplete.
+
+* docutils/writers/latex2e.py: LaTeX Writer, added by Engelbert Gruber
+  (from the sandbox).
+
+  - Added french.
+  - Double quotes in literal blocks (special treatment for de/ngerman).
+  - Added '--hyperlink-color' option ('0' turns off coloring of links).
+  - Added  "--attribution" option.
+  - Right align attributions.
+
+* docutils/writers/pep_html.py:
+
+  - Parameterized output encoding in PEP template.
+  - Reworked substitutions from ``locals()`` into ``subs`` dict.
+  - Redefined "--pep-stylesheet" as containing an invariant URL, used
+    verbatim.  Added "--pep-stylesheet-path", interpreted w.r.t. the
+    working directory.
+  - Added an override on the "--footnote-references" option.
+  - Factored out ``HTMLTranslator.get_stylesheet_reference()``.
+  - Added Docutils version to "generator" meta tag.
+  - Added a "DO NOT EDIT THIS FILE" comment to generated HTML.
+
+* docs/tools.txt:
+
+  - Added a "silent" setting for ``buildhtml.py``.
+  - Added a "Getting Help" section.
+  - Rearranged the structure.
+  - Kept up to date, with new settings, command-line options etc.
+  - Added section for ``rst2latex.py`` (Engelbert Gruber).
+  - Converted settings table into a definition list.
+
+* docs/rst/quickstart.txt:
+
+  - Added a table of contents.
+  - Added feedback information.
+  - Added mention of minimum section title underline lengths.
+  - Removed the 4-character minimum for section title underlines.
+
+* docs/rst/quickref.html:
+
+  - Added a "Getting Help" section.
+  - Added a style to make section title backlinks more subtle.
+  - Added mention of minimum section title underline lengths.
+  - Removed the 4-character minimum for section title underlines.
+
+* extras: Directory added to project; contains third-party modules
+  that Docutils depends on (optparse, textwrap, roman).  These are
+  only installed if they're not already present.
+
+* licenses: Directory added to project; contains copies of license
+  files for non-public-domain files.
+
+* spec/doctree.txt:
+
+  - Changed the focus.  It's about DTD elements:  structural
+    relationships, semantics, and external (public) attributes.  Not
+    about the element class library.
+  - Moved some implementation-specific stuff into ``docutils.nodes``
+    docstrings.
+  - Wrote descriptions of all common attributes and parameter
+    entities.  Filled in introductory material.
+  - Working through the element descriptions: 55 down, 37 to go.
+  - Removed "Representation of Horizontal Rules" to
+    spec/rst/alternatives.txt.
+
+* spec/docutils.dtd:
+
+  - Added "generated" inline element.
+  - Added "line_block" body element.
+  - Added "auto" attribute to "title".
+  - Changed content models of "literal_block" and "doctest_block" to
+    ``%text.model``.
+  - Added ``%number;`` attribute type parameter entity.
+  - Changed ``%structural.elements;`` to ``%section.elements``.
+  - Updated attribute types; made more specific.
+  - Added "address" bibliographic element.
+  - Added "line" attribute to ``system_message`` element.
+  - Removed "field_argument" element; "field_name" may contain
+    multiple words and whitespace.
+  - Changed public identifier to docutils.sf.net.
+  - Removed "interpreted" element; added "title_reference",
+    "abbreviation", "acronym".
+  - Removed "refuri" attribute from "footnote_reference" and
+    "citation_reference".
+  - Added "sidebar", "rubric", "attribution", "admonition",
+    "superscript", "subscript", and "inline" elements.
+
+* spec/pep-0256.txt: Converted to reStructuredText & updated.
+
+* spec/pep-0257.txt: Converted to reStructuredText & updated.
+
+* spec/pep-0258.txt: Converted to reStructuredText & updated.
+
+* spec/semantics.txt: Updated with text from a Doc-SIG response to
+  Dallas Mahrt.
+
+* spec/transforms.txt: Added to project.
+
+* spec/howto: Added subdirectory, for developer how-to docs.
+
+* spec/howto/rst-directives.txt: Added to project.  Original by Dethe
+  Elza, edited & extended by David Goodger.
+
+* spec/howto/i18n.txt: Docutils Internationalization.  Added to
+  project.
+
+* spec/rst/alternatives.txt:
+
+  - Added "Doctree Representation of Transitions" from
+    spec/doctree.txt.
+  - Updated "Inline External Targets" & closed the debate.
+  - Added ideas for interpreted text syntax extensions.
+  - Added "Nested Inline Markup" section.
+
+* spec/rst/directives.txt:
+
+  - Added directives: "topic", "sectnum", "target-notes",
+    "line-block", "parsed-literal", "include", "replace", "sidebar",
+    "admonition", "rubric", "epigraph", "highlights", "unicode" and
+    "class".
+  - Formalized descriptions of directive details.
+  - Added an "align" attribute to the "image" & "figure" directives
+    (by Adam Chodorowski).
+  - Added "class" options to "topic", "sidebar", "line-block",
+    "parsed-literal", "contents", and "image"; and "figclass" to
+    "figure".
+
+* spec/rst/interpreted.txt: Added to project.  Descriptions of
+  interpreted text roles.
+
+* spec/rst/introduction.txt:
+
+  - Added pointers to material for new users.
+
+* spec/rst/reStructuredText.txt:
+
+  - Disambiguated comments (just add a newline after the "::").
+  - Updated enumerated list description; added a discussion of the
+    second-line validity checking.
+  - Updated directive description.
+  - Added a note redirecting newbies to the user docs.
+  - Expanded description of inline markup start-strings in non-markup
+    contexts.
+  - Removed field arguments and made field lists a generic construct.
+  - Removed the 4-character minimum for section title underlines.
+  - Clarified term/classifier delimiter & inline markup ambiguity
+    (definition lists).
+  - Added "Embedded URIs".
+  - Updated "Interpreted Text" section.
+  - Added "Character-Level Inline Markup" section.
+
+* test: Continually adding & updating tests.
+
+  - Moved test/test_rst/ to test/test_parsers/test_rst/.
+  - Moved test/test_pep/ to test/test_readers/test_pep/.
+  - Added test/test_readers/test_python/.
+  - Added test/test_writers/ (Engelbert Gruber).
+
+* tools:
+
+  - Made the ``locale.setlocale()`` calls in front ends
+    fault-tolerant.
+
+* tools/buildhtml.py:
+
+  - Added "--silent" option.
+  - Fixed bug with absolute paths & "--config".
+  - Updated for new I/O classes.
+  - Added some exception handling.
+  - Separated publishers' setting defaults; prevents interference.
+  - Updated for new ``publish_file()`` convenience function.
+
+* tools/pep-html-template:
+
+  - Allow for "--embed-stylesheet".
+  - Added Docutils version to "generator" meta tag.
+  - Added a "DO NOT EDIT THIS FILE" comment to generated HTML.
+  - Conform to XHTML spec.
+
+* tools/pep2html.py:
+
+  - Made ``argv`` a parameter to ``main()``.
+  - Added support for "Content-Type:" header & arbitrary PEP formats.
+  - Linked "Content-Type: text/plain" to PEP 9.
+  - Files skipped (due to an error) are not pushed onto the server.
+  - Updated for new I/O classes.
+  - Added ``check_requirements()`` & ``pep_type_error()``.
+  - Added some exception handling.
+  - Updated for new ``publish_string()`` convenience function.
+  - Added a "DO NOT EDIT THIS FILE" comment to generated HTML.
+
+* tools/quicktest.py:
+
+  - Added "-V"/"--version" option.
+
+* tools/rst2latex.py: LaTeX front end, added by Engelbert Gruber.
+
+* tools/unicode2rstsubs.py: Added to project.  Produces character
+  entity files (reSructuredText substitutions) from the MathML master
+  unicode.xml file.
+
+* tools/editors: Support code for editors, added to project.  Contains
+  ``emacs/restructuredtext.el``.
+
+* tools/stylesheets/default.css: Moved into the stylesheets directory.
+
+  - Added style for chunks of inline literals.
+  - Removed margin for first child of table cells.
+  - Right-aligned field list names.
+  - Support for auto-numbered section titles in TOCs.
+  - Increased the size of inline literals (<tt>) in titles.
+  - Restored the light gray background for inline literals.
+  - Added support for "line_block" elements.
+  - Added style for "address" elements.
+  - Removed "a.footnote-reference" style; doing it with ``<sup>`` now.
+  - Improved field list rendering.
+  - Vertical whitespace improvements.
+  - Removed "a.target" style.
+
+* tools/stylesheets/pep.css:
+
+  - Fixed nested section margins.
+  - Other changes parallel those of ``../default.css``.
+
+
+Release 0.2 (2002-07-31)
+========================
+
+General:
+
+- The word "component" was being used ambiguously.  From now on,
+  "component" will be used to mean "Docutils component", as in Reader,
+  Writer, Parser, or Transform.  Portions of documents (Table of
+  Contents, sections, etc.)  will be called "document parts".
+- Did a grand renaming: a lot of ``verylongnames`` became
+  ``very_long_names``.
+- Cleaned up imports: no more relative package imports or
+  comma-separated lists of top-level modules.
+- Added support for an option values object which carries default
+  settings and overrides (from command-line options and library use).
+- Added internal Unicode support, and support for both input and
+  output encodings.
+- Added support for the ``docutils.io.IO`` class & subclasses.
+
+Specific:
+
+* docutils/__init__.py:
+
+  - Added ``ApplicationError`` and ``DataError``, for use throughout
+    the package.
+  - Added ``Component`` base class for Docutils components; implements
+    the ``supports`` method.
+  - Added ``__version__`` (thus, ``docutils.__version__``).
+
+* docutils/core.py:
+
+  - Removed many keyword parameters to ``Publisher.__init__()`` and
+    ``publish()``; bundled into an option values object.  Added
+    "argv", "usage", "description", and "option_spec" parameters for
+    command-line support.
+  - Added ``Publisher.process_command_line()`` and ``.set_options()``
+    methods.
+  - Reworked I/O model for ``docutils.io`` wrappers.
+  - Updated ``Publisher.set_options()``; now returns option values
+    object.
+  - Added support for configuration files (/etc/docutils.conf,
+    ./docutils.conf, ~/.docutils).
+  - Added ``Publisher.setup_option_parser()``.
+  - Added default usage message and description.
+
+* docutils/frontend.py: Added to project; support for front-end
+  (command-line) scripts.  Option specifications may be augmented by
+  components.  Requires Optik (http://optik.sf.net/) for option
+  processing (installed locally as docutils/optik.py).
+
+* docutils/io.py: Added to project; uniform API for a variety of input
+  output mechanisms.
+
+* docutils/nodes.py:
+
+  - Added ``TreeCopyVisitor`` class.
+  - Added a ``copy`` method to ``Node`` and subclasses.
+  - Added a ``SkipDeparture`` exception for visitors.
+  - Renamed ``TreePruningException`` from ``VisitorException``.
+  - Added docstrings to ``TreePruningException``, subclasses, and
+    ``Nodes.walk()``.
+  - Improved docstrings.
+  - Added ``SparseNodeVisitor``, refined ``NodeVisitor``.
+  - Moved ``utils.id()`` to ``nodes.make_id()`` to avoid circular
+    imports.
+  - Added ``decoration``, ``header``, and ``footer`` node classes, and
+    ``PreDecorative`` mixin.
+  - Reworked the name/id bookkeeping; to ``document``, removed
+    ``explicit_targets`` and ``implicit_targets`` attributes, added
+    ``nametypes`` attribute and ``set_name_id_map`` method.
+  - Added ``NodeFound`` exception, for use with ``NodeVisitor``
+    traversals.
+  - Added ``document.has_name()`` method.
+  - Fixed DOM generation for list-attributes.
+  - Added category class ``Labeled`` (used by footnotes & citations).
+  - Added ``Element.set_class()`` method (sets "class" attribute).
+
+* docutils/optik.py: Added to project.  Combined from the Optik
+  package, with added option groups and other modifications.  The use
+  of this module is probably only temporary.
+
+* docutils/statemachine.py:
+
+  - Added ``runtime_init`` method to ``StateMachine`` and ``State``.
+  - Added underscores to improve many awkward names.
+  - In ``string2lines()``, changed whitespace normalizing translation
+    table to regexp; restores Python 2.0 compatibility with Unicode.
+
+* docutils/urischemes.py:
+
+  - Filled in some descriptions.
+  - Added "shttp" scheme.
+
+* docutils/utils.py:
+
+  - Added ``clean_rcs_keywords`` function (moved from
+    docutils/transforms/frontmatter.py
+    ``DocInfo.filter_rcs_keywords``).
+  - Added underscores to improve many awkward names.
+  - Changed names of Reporter's thresholds:
+    warning_level -> report_level; error_level -> halt_level.
+  - Moved ``utils.id()`` to ``nodes.make_id()``.
+  - Added ``relative_path(source, target)``.
+
+* docutils/languages/de.py: German mappings; added to project.  Thanks
+  to Gunnar Schwant for the translations.
+
+* docutils/languages/en.py: Added "Dedication" bibliographic field
+  mappings.
+
+* docutils/languages/sv.py: Swedish mappings; added to project by Adam
+  Chodorowski.
+
+* docutils/parsers/rst/states.py:
+
+  - Added underscores to improve many awkward names.
+  - Added RFC-2822 header support.
+  - Extracted the inline parsing code from ``RSTState`` to a separate
+    class, ``Inliner``, which will allow easy subclassing.
+  - Made local bindings for ``memo`` container & often-used contents
+    (reduces code complexity a lot).  See ``RSTState.runtime_init()``.
+  - ``RSTState.parent`` replaces ``RSTState.statemachine.node``.
+  - Added ``MarkupMismatch`` exception; for late corrections.
+  - Added ``-/:`` characters to inline markup's start string prefix,
+    ``/`` to end string suffix.
+  - Fixed a footnote bug.
+  - Fixed a bug with literal blocks.
+  - Applied patch from Simon Budig: simplified regexps with symbolic
+    names, removed ``Inliner.groups`` and ``Body.explicit.groups``.
+  - Converted regexps from ``'%s' % var`` to ``'%(var)s' % locals()``.
+  - Fixed a bug in ``Inliner.interpreted_or_phrase_ref()``.
+  - Allowed non-ASCII in "simple names" (directive names, field names,
+    references, etc.).
+  - Converted ``Inliner.patterns.initial`` to be dynamically built
+    from parts with ``build_regexp()`` function.
+  - Changed ``Inliner.inline_target`` to ``.inline_internal_target``.
+  - Updated docstrings.
+  - Changed "table" to "grid_table"; added "simple_table" support.
+
+* docutils/parsers/rst/tableparser.py:
+
+  - Changed ``TableParser`` to ``GridTableParser``.
+  - Added ``SimpleTableParser``.
+  - Refactored naming.
+
+* docutils/parsers/rst/directives/__init__.py: Added "en" (English) as
+  a fallback language for directive names.
+
+* docutils/parsers/rst/directives/html.py: Changed the ``meta``
+  directive to use a ``pending`` element, used only by HTML writers.
+
+* docutils/parsers/rst/directives/parts.py: Renamed from
+  components.py.
+
+  - Added "backlinks" attribute to "contents" directive.
+
+* docutils/parsers/rst/languages/sv.py: Swedish mappings; added to
+  project by Adam Chodorowski.
+
+* docutils/readers/__init__.py: Gave Readers more control over
+  choosing and instantiating Parsers.
+
+* docutils/readers/pep.py: Added to project; for PEP processing.
+
+* docutils/transforms/__init__.py: ``Transform.__init__()`` now
+  requires a ``component`` parameter.
+
+* docutils/transforms/components.py: Added to project; transforms
+  related to Docutils components.
+
+* docutils/transforms/frontmatter.py:
+
+  - In ``DocInfo.extract_authors``, check for a single "author" in an
+    "authors" group, and convert it to a single "author" element.
+  - Added support for "Dedication" and generic bibliographic fields.
+
+* docutils/transforms/peps.py: Added to project; PEP-specific.
+
+* docutils/transforms/parts.py: Renamed from old components.py.
+
+  - Added filter for `Contents`, to use alt-text for inline images,
+    and to remove inline markup that doesn't make sense in the ToC.
+  - Added "name" attribute to TOC topic depending on its title.
+  - Added support for optional TOC backlinks.
+
+* docutils/transforms/references.py: Fixed indirect target resolution
+  in ``Hyperlinks`` transform.
+
+* docutils/transforms/universal.py:
+
+  - Changed ``Messages`` transform to properly filter out system
+    messages below the warning threshold.
+  - Added ``Decorations`` transform (support for ``--generator``,
+    ``--date``, ``--time``, ``--source-link`` options).
+
+* docutils/writers/__init__.py: Added "pdf" alias in anticipation of
+  Engelbert Gruber's PDF writer.
+
+* docutils/writers/html4css1.py:
+
+  - Made XHTML-compatible (switched to lowercase element & attribute
+    names; empty tag format).
+  - Escape double-dashes in comment text.
+  - Improved boilerplate & modularity of output.
+  - Exposed modular output in Writer class.
+  - Added a "generator" meta tag to <head>.
+  - Added support for the ``--stylesheet`` option.
+  - Added support for ``decoration``, ``header``, and ``footer``
+    elements.
+  - In ``HTMLTranslator.attval()``, changed whitespace normalizing
+    translation table to regexp; restores Python 2.0 compatibility
+    with Unicode.
+  - Added the translator class as instance variable to the Writer, to
+    make it easily subclassable.
+  - Improved option list spacing (thanks to Richard Jones).
+  - Modified field list output.
+  - Added backlinks to footnotes & citations.
+  - Added percentage widths to "<col>" tags (from colspec).
+  - Option lists: "<code>" changed to "<kbd>", ``option_argument``
+    "<span>" changed to "<var>".
+  - Inline literals: "<code>" changed to "<tt>".
+  - Many changes to optimize vertical space: compact simple lists etc.
+  - Add a command-line options & directive attributes to control TOC
+    and footnote/citation backlinks.
+  - Added support for optional footnote/citation backlinks.
+  - Added support for generic bibliographic fields.
+  - Identify backrefs.
+  - Relative URLs for stylesheet links.
+
+* docutils/writers/pep_html.py: Added to project; HTML Writer for
+  PEPs (subclass of ``html4css1.Writer``).
+
+* docutils/writers/pseudoxml.py: Renamed from pprint.py.
+
+* docutils/writers/docutils_xml.py: Added to project; trivial writer
+  of the Docutils internal doctree in XML.
+
+* docs/tools.txt: "Docutils Front-End Tools", added to project.
+
+* spec/doctree.txt:
+
+  - Changed the title to "The Docutils Document Tree".
+  - Added "Hyperlink Bookkeeping" section.
+
+* spec/docutils.dtd:
+
+  - Added ``decoration``, ``header``, and ``footer`` elements.
+  - Brought ``interpreted`` element in line with the parser: changed
+    attribute "type" to "role", added "position".
+  - Added support for generic bibliographic fields.
+
+* spec/notes.txt: Continual updates.  Added "Project Policies".
+
+* spec/pep-0256.txt:  Updated.  Added "Roadmap to the Doctring PEPs"
+  section.
+
+* spec/pep-0257.txt: Clarified prohibition of signature repetition.
+
+* spec/pep-0258.txt: Updated.  Added text from pysource.txt and
+  mailing list discussions.
+
+* spec/pep-0287.txt:
+
+  - Renamed to "reStructuredText Docstring Format".
+  - Minor edits.
+  - Reworked Q&A as an enumerated list.
+  - Converted to reStructuredText format.
+
+* spec/pysource.dtd:
+
+  - Reworked structural elements, incorporating ideas from Tony Ibbs.
+
+* spec/pysource.txt: Removed from project.  Moved much of its contents
+  to pep-0258.txt.
+
+* spec/rst/alternatives.txt:
+
+  - Expanded auto-enumerated list idea; thanks to Fred Bremmer.
+  - Added "Inline External Targets" section.
+
+* spec/rst/directives.txt:
+
+  - Added "backlinks" attribute to "contents" directive.
+
+* spec/rst/problems.txt:
+
+  - Updated the Enumerated List Markup discussion.
+  - Added new alternative table markup syntaxes.
+
+* spec/rst/reStructuredText.txt:
+
+  - Clarified field list usage.
+  - Updated enumerated list description.
+  - Clarified purpose of directives.
+  - Added ``-/:`` characters to inline markup's start string prefix,
+    ``/`` to end string suffix.
+  - Updated "Authors" bibliographic field behavior.
+  - Changed "inline hyperlink targets" to "inline internal targets".
+  - Added "simple table" syntax to supplement the existing but
+    newly-renamed "grid tables".
+  - Added cautions for anonymous hyperlink use.
+  - Added "Dedication" and generic bibliographic fields.
+
+* test: Made test modules standalone (subdirectories became packages).
+
+* test/DocutilsTestSupport.py:
+
+  - Added support for PEP extensions to reStructuredText.
+  - Added support for simple tables.
+  - Refactored naming.
+
+* test/package_unittest.py: Renamed from UnitTestFolder.py.
+
+  - Now supports true packages containing test modules
+    (``__init__.py`` files required); fixes duplicate module name bug.
+
+* test/test_pep/: Subpackage added to project; PEP testing.
+
+* test/test_rst/test_SimpleTableParser.py: Added to project.
+
+* tools:
+
+  - Updated html.py and publish.py front-end tools to use the new
+    command-line processing facilities of ``docutils.frontend``
+    (exposed in ``docutils.core.Publisher``), reducing each to just a
+    few lines of code.
+  - Added ``locale.setlocale()`` calls to front-end tools.
+
+* tools/buildhtml.py: Added to project; batch-generates .html from all
+  the .txt files in directories and subdirectories.
+
+* tools/default.css:
+
+  - Added support for ``header`` and ``footer`` elements.
+  - Added styles for "Dedication" topics (biblio fields).
+
+* tools/docutils.conf: A configuration file; added to project.
+
+* tools/docutils-xml.py: Added to project.
+
+* tools/pep.py: Added to project; PEP to HTML front-end tool.
+
+* tools/pep-html-template: Added to project.
+
+* tools/pep2html.py: Added to project from Python (nondist/peps).
+  Added support for Docutils (reStructuredText PEPs).
+
+* tools/quicktest.py:
+
+  - Added the ``--attributes`` option, hacked a bit.
+  - Added a second command-line argument (output file); cleaned up.
+
+* tools/stylesheets/: Subdirectory added to project.
+
+* tools/stylesheets/pep.css: Added to project; stylesheet for PEPs.
+
+
+Release 0.1 (2002-04-20)
+========================
+
+This is the first release of Docutils, merged from the now inactive
+reStructuredText__ and `Docstring Processing System`__ projects.  For
+the pre-Docutils history, see the `reStructuredText HISTORY`__ and the
+`DPS HISTORY`__ files.
+
+__ http://structuredtext.sourceforge.net/
+__ http://docstring.sourceforge.net/
+__ http://structuredtext.sourceforge.net/HISTORY.html
+__ http://docstring.sourceforge.net/HISTORY.html
+
+General changes: renamed 'dps' package to 'docutils'; renamed
+'restructuredtext' subpackage to 'rst'; merged the codebases; merged
+the test suites (reStructuredText's test/test_states renamed to
+test/test_rst); and all modifications required to make it all work.
+
+* docutils/parsers/rst/states.py:
+
+  - Improved diagnostic system messages for missing blank lines.
+  - Fixed substitution_reference bug.
+
+
+..
+   Local Variables:
+   mode: indented-text
+   indent-tabs-mode: nil
+   sentence-end-double-space: t
+   fill-column: 70
+   End:

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/PKG-INFO
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/PKG-INFO	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/PKG-INFO	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,38 @@
+Metadata-Version: 1.0
+Name: docutils
+Version: 0.3.7
+Summary: Docutils -- Python Documentation Utilities
+Home-page: http://docutils.sourceforge.net/
+Author: David Goodger
+Author-email: goodger at users.sourceforge.net
+License: public domain, Python, BSD, GPL (see COPYING.txt)
+Description: Docutils is a modular system for processing documentation
+        into useful formats, such as HTML, XML, and LaTeX.  For
+        input Docutils supports reStructuredText, an easy-to-read,
+        what-you-see-is-what-you-get plaintext markup syntax.
+Platform: OS-independent
+Classifier: Development Status :: 3 - Alpha
+Classifier: Environment :: Console
+Classifier: Intended Audience :: End Users/Desktop
+Classifier: Intended Audience :: Other Audience
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: Public Domain
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: License :: OSI Approved :: BSD License
+Classifier: License :: OSI Approved :: GNU General Public License (GPL)
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Documentation
+Classifier: Topic :: Software Development :: Documentation
+Classifier: Topic :: Text Processing
+Classifier: Natural Language :: English
+Classifier: Natural Language :: Afrikaans
+Classifier: Natural Language :: Esperanto
+Classifier: Natural Language :: French
+Classifier: Natural Language :: German
+Classifier: Natural Language :: Italian
+Classifier: Natural Language :: Russian
+Classifier: Natural Language :: Slovak
+Classifier: Natural Language :: Spanish
+Classifier: Natural Language :: Swedish

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/README.txt
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/README.txt	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/README.txt	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,370 @@
+==================
+ README: Docutils
+==================
+
+:Author: David Goodger
+:Contact: goodger at users.sourceforge.net
+:Date: $Date: 2005/01/07 15:11:43 $
+:Web site: http://docutils.sourceforge.net/
+:Copyright: This document has been placed in the public domain.
+
+.. contents::
+
+
+Thank you for downloading the Python Docutils project archive.  As
+this is a work in progress, please check the project website for
+updated working files (snapshots).  This project should be considered
+highly experimental; APIs are subject to change at any time.
+
+
+Quick-Start
+===========
+
+This is for those who want to get up & running quickly.  Read on for
+complete details.
+
+1. Get and install the latest release of Python, available from
+
+       http://www.python.org/
+
+   Python 2.2 or later [1]_ is required; Python 2.2.2 or later is
+   recommended.
+
+2. Use the latest Docutils code.  Get the code from CVS or from the
+   snapshot:
+
+       http://docutils.sf.net/docutils-snapshot.tgz
+
+   See `Releases & Snapshots`_ below for details.
+
+3. Unpack the tarball in a temporary directory (**not** directly in
+   Python's ``site-packages``) and install with the standard ::
+
+       python setup.py install
+
+   See Installation_ below for details.
+
+4. Use a front-end tool from the "tools" subdirectory of the same
+   directory as in step 3.  For example::
+
+       cd tools
+       ./rst2html.py ../FAQ.txt ../FAQ.html        (Unix)
+       python rst2html.py ..\FAQ.txt ..\FAQ.html   (Windows)
+
+   See Usage_ below for details.
+
+
+Purpose
+=======
+
+The purpose of the Docutils project is to create a set of tools for
+processing plaintext documentation into useful formats, such as HTML,
+XML, and TeX.  Support for the following sources has been implemented:
+
+* Standalone files.
+
+* `PEPs (Python Enhancement Proposals)`_.
+
+Support for the following sources is planned:
+
+* Inline documentation from Python modules and packages, extracted
+  with namespace context.  **This is the focus of the current
+  development effort.**
+
+* Email (RFC-822 headers, quoted excerpts, signatures, MIME parts).
+
+* Wikis, with global reference lookups of "wiki links".
+
+* Compound documents, such as multiple chapter files merged into a
+  book.
+
+* And others as discovered.
+
+.. _PEPs (Python Enhancement Proposals):
+   http://www.python.org/peps/pep-0012.html
+
+
+Releases & Snapshots
+====================
+
+Putting together an official "Release" of Docutils is a significant
+effort, so it isn't done that often.  In the meantime, the CVS
+snapshots always contain the latest code and documentation, usually
+updated within an hour of changes being committed to the repository,
+and usually bug-free:
+
+* Snapshot of Docutils code, documentation, front-end tools, and
+  tests: http://docutils.sf.net/docutils-snapshot.tgz
+
+* Snapshot of the Sandbox (experimental, contributed code):
+  http://docutils.sf.net/docutils-sandbox-snapshot.tgz
+
+* Snapshot of web files (the files that generate the web site):
+  http://docutils.sf.net/docutils-web-snapshot.tgz
+
+To keep up to date on the latest developments, download fresh copies
+of the snapshots regularly.  New functionality is being added weekly,
+sometimes daily.  (There's also the CVS repository, and a mailing list
+for CVS messages.  See the web site [address above] or
+docs/dev/policies.txt for details.)
+
+
+Requirements
+============
+
+To run the code, Python 2.2 or later [1]_ must already be installed.
+The latest release is recommended.  Python is available from
+http://www.python.org/.
+
+The `Python Imaging Library`, or PIL, is used for some image
+manipulation operations if it is installed.
+
+Docutils uses Greg Ward's Optik_/optparse option processing package.
+It is included in the Docutils distribution.  Python 2.3 and later
+come with optparse in the standard library; in this case, the Docutils
+copy is not installed.
+
+.. [1] Python 2.1 may be used providing the compiler package is
+   installed.  The compiler package can be found in the Tools/
+   directory of Python 2.1's source distribution.
+
+.. _Python Imaging Library: http://www.pythonware.com/products/pil/
+.. _Optik: http://optik.sourceforge.net/
+
+
+Project Files & Directories
+===========================
+
+* README.txt: You're reading it.
+
+* COPYING.txt: Public Domain Dedication and copyright details for
+  non-public-domain files (most are PD).
+
+* FAQ.txt: Docutils Frequently Asked Questions.
+
+* HISTORY.txt: Release notes for the current and previous project
+  releases.
+
+* setup.py: Installation script.  See "Installation" below.
+
+* install.py: Quick & dirty installation script.  Just run it.  For
+  any kind of customization or help though, setup.py must be used.
+
+* docutils: The project source directory, installed as a Python
+  package.
+
+* extras: Directory for third-party modules that Docutils depends on.
+  These are only installed if they're not already present.
+
+* docs: The project documentation directory.  Read ``docs/index.txt``
+  for an overview, which is especially interesting for developers.
+
+* docs/user: The project user documentation directory.  Contains the
+  following documents, among others:
+
+  - docs/user/tools.txt: Docutils Front-End Tools
+  - docs/user/latex.txt: Docutils LaTeX Writer
+  - docs/user/rst/quickstart.txt: A ReStructuredText Primer
+  - docs/user/rst/quickref.html: Quick reStructuredText (HTML only)
+
+* docs/ref: The project reference directory.
+  ``docs/ref/rst/restructuredtext.txt`` is the reStructuredText
+  reference.
+
+* licenses: Directory containing copies of license files for
+  non-public-domain files.
+
+* tools: Directory for Docutils front-end tools.  See
+  ``docs/user/tools.txt`` for documentation.
+
+* test: Unit tests.  Not required to use the software, but very useful
+  if you're planning to modify it.  See `Running the Test Suite`_
+  below.
+
+
+Installation
+============
+
+The first step is to expand the ``.tar.gz`` or ``.tgz`` archive in a
+temporary directory (**not** directly in Python's ``site-packages``).
+It contains a distutils setup file "setup.py".  OS-specific
+installation instructions follow.
+
+
+GNU/Linux, BSDs, Unix, Mac OS X, etc.
+-------------------------------------
+
+1. Open a shell.
+
+2. Go to the directory created by expanding the archive::
+
+       cd <archive_directory_path>
+
+3. Install the package::
+
+       python setup.py install
+
+   If the python executable isn't on your path, you'll have to specify
+   the complete path, such as /usr/local/bin/python.  You may need
+   root permissions to complete this step.
+
+You can also just run install.py; it does the same thing.
+
+
+Windows
+-------
+
+1. Open a DOS box (Command Shell, MSDOS Prompt, or whatever they're
+   calling it these days).
+
+2. Go to the directory created by expanding the archive::
+
+       cd <archive_directory_path>
+
+3. Install the package::
+
+       <path_to_python.exe>\python setup.py install
+
+If your system is set up to run Python when you double-click on .py
+files, you can run install.py to do the same as the above.
+
+
+Mac OS 8/9
+----------
+
+1. Open the folder containing the expanded archive.
+
+2. Double-click on the file "setup.py", which should be a "Python
+   module" file.
+
+   If the file isn't a "Python module", the line endings are probably
+   also wrong, and you will need to set up your system to recognize
+   ".py" file extensions as Python files.  See
+   http://gotools.sourceforge.net/mac/python.html for detailed
+   instructions.  Once set up, it's easiest to start over by expanding
+   the archive again.
+
+3. The distutils options window will appear.  From the "Command" popup
+   list choose "install", click "Add", then click "OK".
+
+If install.py is a "Python module" (see step 2 above if it isn't), you
+can run it (double-click) instead of the above.  The distutils options
+window will not appear.
+
+
+Usage
+=====
+
+After unpacking and installing the Docutils package, the following
+shell commands will generate HTML for all included documentation::
+
+    cd <archive_directory_path>/tools
+    ./buildhtml.py ../
+
+On Windows systems, type::
+
+    cd <archive_directory_path>\tools
+    python buildhtml.py ..
+
+The final directory name of the ``<archive_directory_path>`` is
+"docutils" for snapshots.  For official releases, the directory may be
+called "docutils-X.Y.Z", where "X.Y.Z" is the release version.
+Alternatively::
+
+    cd <archive_directory_path>
+    tools/buildhtml.py --config=tools/docutils.conf          (Unix)
+    python tools\buildhtml.py --config=tools\docutils.conf   (Windows)
+
+Some files may generate system messages (warnings and errors).  The
+``docs/user/rst/demo.txt`` file (under the archive directory) contains
+5 intentional errors.  (They test the error reporting mechanism!)
+
+There are many front-end tools in the unpacked "tools" subdirectory.
+You may want to begin with the "rst2html.py" front-end tool.  Most
+tools take up to two arguments, the source path and destination path,
+with STDIN and STDOUT being the defaults.  Use the "--help" option to
+the front-end tools for details on options and arguments.  See
+Docutils Front-End Tools (``docs/user/tools.txt``) for full documentation.
+
+The package modules are continually growing and evolving.  The
+``docutils.statemachine`` module is usable independently.  It contains
+extensive inline documentation (in reStructuredText format of course).
+
+Contributions are welcome!
+
+
+Running the Test Suite
+======================
+
+To run the entire test suite, after installation_ open a shell and use
+the following commands::
+
+    cd <archive_directory_path>/test
+    ./alltests.py
+
+Under Windows, type::
+
+    cd <archive_directory_path>\test
+    python alltests.py
+
+You should see a long line of periods, one for each test, and then a
+summary like this::
+
+    Ran 518 tests in 24.653s
+
+    OK
+    Elapsed time: 26.189 seconds
+
+The number of tests will grow over time, and the times reported will
+depend on the computer running the tests.  The difference between the
+two times represents the time required to set up the tests (import
+modules, create data structures, etc.).
+
+If any of the tests fail, please `open a bug report`_ or `send email`_
+[2]_.  Please include all relevant output, information about your
+operating system, Python version, and Docutils version.  To see the
+Docutils version, use these commands in the shell::
+
+    cd ../tools
+    ./quicktest.py --version
+
+Windows users type these commands::
+
+    cd ..\tools
+    python quicktest.py --version
+
+.. _open a bug report:
+   http://sourceforge.net/tracker/?group_id=38414&atid=422030
+.. _send email: mailto:docutils-users at lists.sourceforge.net
+   ?subject=Docutils%20test%20suite%20failure
+
+
+Getting Help
+============
+
+If you have questions or need assistance with Docutils or
+reStructuredText, please `post a message`_ to the `Docutils-Users
+mailing list`_ [2]_.
+
+.. [2] Due to overwhelming amounts of spam, the
+   docutils-users at lists.sourceforge.net mailing list has been set up
+   for subscriber posting only.  Non-subscribers who post to
+   docutils-users will receive a message with "Subject: Your message
+   to Docutils-users awaits moderator approval".  Legitimate messages
+   are accepted and posted as soon as possible (a list administrator
+   must verify the message manually).  If you'd like to subscribe to
+   docutils-users, please visit
+   <http://lists.sourceforge.net/lists/listinfo/docutils-users>.
+
+.. _post a message: mailto:docutils-users at lists.sourceforge.net
+.. _Docutils-Users mailing list:
+   http://lists.sourceforge.net/lists/listinfo/docutils-users
+
+
+..
+   Local Variables:
+   mode: indented-text
+   indent-tabs-mode: nil
+   sentence-end-double-space: t
+   fill-column: 70
+   End:

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/THANKS.txt
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/THANKS.txt	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/THANKS.txt	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,138 @@
+Acknowledgements
+================
+
+:Author: David Goodger
+:Contact: goodger at python.org
+:Date: $Date: 2005/01/07 15:11:43 $
+:Revision: $Revision: 1.1.2.1 $
+:Copyright: This document has been placed in the public domain.
+
+I would like to acknowledge the people who have made a direct impact
+on the Docutils project, knowingly or not, in terms of encouragement,
+suggestions, criticism, bug reports, code contributions, cash
+donations, tasty treats, and related projects:
+
+* Aahz
+* David Abrahams
+* David Ascher
+* Heiko Baumann
+* Eric Bellot
+* Ian Bicking
+* Marek Blaha
+* Martin Blais
+* Stephen Boulet
+* Fred Bremmer
+* Simon Budig
+* Bill Bumgarner
+* Brett Cannon
+* Greg Chapman
+* Nicolas Chauveau
+* Beni Cherniavsky
+* Adam Chodorowski
+* Brent Cook
+* Laura Creighton
+* Artur de Sousa Rocha
+* Stephan Deibel & `Wing IDE <http://wingide.com/>`__
+* Jason Diamond
+* William Dode
+* Fred Drake
+* Reggie Dugard
+* Dethe Elza
+* Marcus Ertl
+* Benja Fallenstein
+* fantasai
+* Stefane Fermigier
+* Jim Fulton
+* Peter Funk
+* Lele Gaifax
+* Dinu C. Gherman
+* Matt Gilbert
+* Jorge Gonzalez
+* Engelbert Gruber
+* Jacob Hallen
+* Simon Hefti
+* Doug Hellmann
+* Marc Herbert
+* Juergen Hermann
+* Jannie Hofmeyr
+* Steve Holden
+* Michael Hudson
+* Marcelo Huerta San Martin
+* Ludger Humbert
+* Jeremy Hylton
+* Tony Ibbs
+* Alan Jaffray
+* Joe YS Jaw
+* Dmitry Jemerov
+* Richard Jones
+* Andreas Jung
+* Garth Kidd
+* Axel Kollmorgen
+* Jeff Kowalczyk
+* Dave Kuhlman
+* Lloyd Kvam
+* Kirill Lapshin
+* Nicola Larosa
+* Daniel Larsson
+* Marc-Andre Lemburg
+* Julien Letessier
+* Wolfgang Lipp
+* Edward Loper
+* Dallas Mahrt
+* Ken Manheimer
+* Bob Marshall
+* Mark McEahern
+* Vincent McIntyre
+* John F Meinel Jr
+* Skip Montanaro
+* Paul Moore
+* Nigel W. Moriarty
+* Mark Nodine
+* Patrick K. O'Brien
+* Michel Pelletier
+* Sam Penrose
+* Tim Peters
+* Pearu Peterson
+* Mark Pilgrim
+* Brett g Porter
+* David Priest
+* Jens Quade
+* Andy Robinson
+* Tavis Rudd
+* Tracy Ruggles
+* Oliver Rutherfurd
+* Luc Saffre
+* Kenichi Sato
+* Ueli Schlaepfer
+* Gunnar Schwant
+* Bill Sconce
+* Frank Siebenlist
+* Bruce Smith
+* Asko Soukka
+* Darek Suchojad
+* Roman Suzi
+* Janet Swisher
+* tav
+* Kent Tenney
+* Bob Tolbert
+* Paul Tremblay
+* Laurence Tratt
+* Adrian van den Dries
+* Guido van Rossum
+* Miroslav Vasko
+* Paul Viren
+* Martin von Loewis
+* Greg Ward
+* Barry Warsaw
+* Edward Welbourne
+* Felix Wiemann
+* Ka-Ping Yee
+* Moshe Zadka
+
+Thank you!
+
+Special thanks to `SourceForge <http://sourceforge.net>`__ and the
+`Python Software Foundation <http://www.python.org/psf/>`__.
+
+Hopefully I haven't forgotten anyone or misspelled any names;
+apologies (and please let me know!) if I have.

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,182 @@
+# Author: David Goodger
+# Contact: goodger at python.org
+# Revision: $Revision: 1.2.10.9 $
+# Date: $Date: 2005/01/07 13:26:01 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This is the Docutils (Python Documentation Utilities) package.
+
+Package Structure
+=================
+
+Modules:
+
+- __init__.py: Contains component base classes, exception classes, and
+  Docutils `__version__`.
+
+- core.py: Contains the ``Publisher`` class and ``publish_*()`` convenience
+  functions.
+
+- frontend.py: Runtime settings (command-line interface, configuration files)
+  processing, for Docutils front-ends.
+
+- io.py: Provides a uniform API for low-level input and output.
+
+- nodes.py: Docutils document tree (doctree) node class library.
+
+- statemachine.py: A finite state machine specialized for
+  regular-expression-based text filters.
+
+- urischemes.py: Contains a complete mapping of known URI addressing
+  scheme names to descriptions.
+
+- utils.py: Contains the ``Reporter`` system warning class and miscellaneous
+  utilities.
+
+Subpackages:
+
+- languages: Language-specific mappings of terms.
+
+- parsers: Syntax-specific input parser modules or packages.
+
+- readers: Context-specific input handlers which understand the data
+  source and manage a parser.
+
+- transforms: Modules used by readers and writers to modify DPS
+  doctrees.
+
+- writers: Format-specific output translators.
+"""
+
+__docformat__ = 'reStructuredText'
+
+__version__ = '0.3.7'
+"""``major.minor.micro`` version number.  The micro number is bumped for API
+changes, for new functionality, and for interim project releases.  The minor
+number is bumped whenever there is a significant project release.  The major
+number will be bumped when the project is feature-complete, and perhaps if
+there is a major change in the design."""
+
+
+class ApplicationError(StandardError): pass
+class DataError(ApplicationError): pass
+
+
+class SettingsSpec:
+
+    """
+    Runtime setting specification base class.
+
+    SettingsSpec subclass objects used by `docutils.frontend.OptionParser`.
+    """
+
+    settings_spec = ()
+    """Runtime settings specification.  Override in subclasses.
+
+    Defines runtime settings and associated command-line options, as used by
+    `docutils.frontend.OptionParser`.  This is a tuple of:
+
+    - Option group title (string or `None` which implies no group, just a list
+      of single options).
+    
+    - Description (string or `None`).
+    
+    - A sequence of option tuples.  Each consists of:
+
+      - Help text (string)
+      
+      - List of option strings (e.g. ``['-Q', '--quux']``).
+      
+      - Dictionary of keyword arguments.  It contains arguments to the
+        OptionParser/OptionGroup ``add_option`` method, possibly with the
+        addition of a 'validator' keyword (see the
+        `docutils.frontend.OptionParser.validators` instance attribute).  Runtime
+        settings names are derived implicitly from long option names
+        ('--a-setting' becomes ``settings.a_setting``) or explicitly from the
+        'dest' keyword argument.  See optparse docs for more details.
+
+    - More triples of group title, description, options, as many times as
+      needed.  Thus, `settings_spec` tuples can be simply concatenated.
+    """
+
+    settings_defaults = None
+    """A dictionary of defaults for settings not in `settings_spec` (internal
+    settings, intended to be inaccessible by command-line and config file).
+    Override in subclasses."""
+
+    settings_default_overrides = None
+    """A dictionary of auxiliary defaults, to override defaults for settings
+    defined in other components.  Override in subclasses."""
+
+    relative_path_settings = ()
+    """Settings containing filesystem paths.  Override in subclasses.
+    Settings listed here are to be interpreted relative to the current working
+    directory."""
+
+    config_section = None
+    """The name of the config file section specific to this component
+    (lowercase, no brackets).  Override in subclasses."""
+
+    config_section_dependencies = None
+    """A list of names of config file sections that are to be applied before
+    `config_section`, in order (from general to specific).  In other words,
+    the settings in `config_section` are to be overlaid on top of the settings
+    from these sections.  The "general" section is assumed implicitly.
+    Override in subclasses."""
+
+
+class TransformSpec:
+
+    """
+    Runtime transform specification base class.
+
+    TransformSpec subclass objects used by `docutils.transforms.Transformer`.
+    """
+
+    default_transforms = ()
+    """Transforms required by this class.  Override in subclasses."""
+    
+    unknown_reference_resolvers = ()
+    """List of functions to try to resolve unknown references.  Unknown
+    references have a 'refname' attribute which doesn't correspond to any
+    target in the document.  Called when FinalCheckVisitor is unable to find a
+    correct target.  The list should contain functions which will try to
+    resolve unknown references, with the following signature::
+
+        def reference_resolver(node):
+            '''Returns boolean: true if resolved, false if not.'''
+
+    If the function is able to resolve the reference, it should also remove
+    the 'refname' attribute and mark the node as resolved::
+
+        del node['refname']
+        node.resolved = 1
+
+    Each function must have a "priority" attribute which will affect the order
+    the unknown_reference_resolvers are run::
+
+        reference_resolver.priority = 100
+
+    Override in subclasses."""
+
+
+class Component(SettingsSpec, TransformSpec):
+
+    """Base class for Docutils components."""
+
+    component_type = None
+    """Name of the component type ('reader', 'parser', 'writer').  Override in
+    subclasses."""
+
+    supported = ()
+    """Names for this component.  Override in subclasses."""
+    
+    def supports(self, format):
+        """
+        Is `format` supported by this component?
+
+        To be used by transforms to ask the dependent component if it supports
+        a certain input context or output format.
+        """
+        return format in self.supported

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/core.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/core.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/core.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,514 @@
+# Authors: David Goodger
+# Contact: goodger at python.org
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:01 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Calling the ``publish_*`` convenience functions (or instantiating a
+`Publisher` object) with component names will result in default
+behavior.  For custom behavior (setting component options), create
+custom component objects first, and pass *them* to
+``publish_*``/`Publisher`.  See `The Docutils Publisher`_.
+
+.. _The Docutils Publisher: http://docutils.sf.net/docs/api/publisher.html
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import pprint
+from docutils import __version__, SettingsSpec
+from docutils import frontend, io, utils, readers, writers
+from docutils.frontend import OptionParser
+
+
+class Publisher:
+
+    """
+    A facade encapsulating the high-level logic of a Docutils system.
+    """
+
+    def __init__(self, reader=None, parser=None, writer=None,
+                 source=None, source_class=io.FileInput,
+                 destination=None, destination_class=io.FileOutput,
+                 settings=None):
+        """
+        Initial setup.  If any of `reader`, `parser`, or `writer` are not
+        specified, the corresponding ``set_...`` method should be called with
+        a component name (`set_reader` sets the parser as well).
+        """
+
+        self.reader = reader
+        """A `docutils.readers.Reader` instance."""
+
+        self.parser = parser
+        """A `docutils.parsers.Parser` instance."""
+
+        self.writer = writer
+        """A `docutils.writers.Writer` instance."""
+
+        self.source = source
+        """The source of input data, a `docutils.io.Input` instance."""
+
+        self.source_class = source_class
+        """The class for dynamically created source objects."""
+
+        self.destination = destination
+        """The destination for docutils output, a `docutils.io.Output`
+        instance."""
+
+        self.destination_class = destination_class
+        """The class for dynamically created destination objects."""
+
+        self.settings = settings
+        """An object containing Docutils settings as instance attributes.
+        Set by `self.process_command_line()` or `self.get_settings()`."""
+
+    def set_reader(self, reader_name, parser, parser_name):
+        """Set `self.reader` by name."""
+        reader_class = readers.get_reader_class(reader_name)
+        self.reader = reader_class(parser, parser_name)
+        self.parser = self.reader.parser
+
+    def set_writer(self, writer_name):
+        """Set `self.writer` by name."""
+        writer_class = writers.get_writer_class(writer_name)
+        self.writer = writer_class()
+
+    def set_components(self, reader_name, parser_name, writer_name):
+        if self.reader is None:
+            self.set_reader(reader_name, self.parser, parser_name)
+        if self.parser is None:
+            if self.reader.parser is None:
+                self.reader.set_parser(parser_name)
+            self.parser = self.reader.parser
+        if self.writer is None:
+            self.set_writer(writer_name)
+
+    def setup_option_parser(self, usage=None, description=None,
+                            settings_spec=None, config_section=None,
+                            **defaults):
+        if config_section:
+            if not settings_spec:
+                settings_spec = SettingsSpec()
+            settings_spec.config_section = config_section
+            parts = config_section.split()
+            if len(parts) > 1 and parts[-1] == 'application':
+                settings_spec.config_section_dependencies = ['applications']
+        #@@@ Add self.source & self.destination to components in future?
+        option_parser = OptionParser(
+            components=(self.parser, self.reader, self.writer, settings_spec),
+            defaults=defaults, read_config_files=1,
+            usage=usage, description=description)
+        return option_parser
+
+    def get_settings(self, usage=None, description=None,
+                     settings_spec=None, config_section=None, **defaults):
+        """
+        Set and return default settings (overrides in `defaults` dict).
+
+        Set components first (`self.set_reader` & `self.set_writer`).
+        Explicitly setting `self.settings` disables command line option
+        processing from `self.publish()`.
+        """
+        option_parser = self.setup_option_parser(
+            usage, description, settings_spec, config_section, **defaults)
+        self.settings = option_parser.get_default_values()
+        return self.settings
+
+    def process_programmatic_settings(self, settings_spec,
+                                      settings_overrides,
+                                      config_section):
+        if self.settings is None:
+            defaults = (settings_overrides or {}).copy()
+            # Propagate exceptions by default when used programmatically:
+            defaults.setdefault('traceback', 1)
+            self.get_settings(settings_spec=settings_spec,
+                              config_section=config_section,
+                              **defaults)
+
+    def process_command_line(self, argv=None, usage=None, description=None,
+                             settings_spec=None, config_section=None,
+                             **defaults):
+        """
+        Pass an empty list to `argv` to avoid reading `sys.argv` (the
+        default).
+
+        Set components first (`self.set_reader` & `self.set_writer`).
+        """
+        option_parser = self.setup_option_parser(
+            usage, description, settings_spec, config_section, **defaults)
+        if argv is None:
+            argv = sys.argv[1:]
+        self.settings = option_parser.parse_args(argv)
+
+    def set_io(self, source_path=None, destination_path=None):
+        if self.source is None:
+            self.set_source(source_path=source_path)
+        if self.destination is None:
+            self.set_destination(destination_path=destination_path)
+
+    def set_source(self, source=None, source_path=None):
+        if source_path is None:
+            source_path = self.settings._source
+        else:
+            self.settings._source = source_path
+        self.source = self.source_class(
+            source=source, source_path=source_path,
+            encoding=self.settings.input_encoding)
+
+    def set_destination(self, destination=None, destination_path=None):
+        if destination_path is None:
+            destination_path = self.settings._destination
+        else:
+            self.settings._destination = destination_path
+        self.destination = self.destination_class(
+            destination=destination, destination_path=destination_path,
+            encoding=self.settings.output_encoding,
+            error_handler=self.settings.output_encoding_error_handler)
+
+    def apply_transforms(self, document):
+        document.transformer.populate_from_components(
+            (self.source, self.reader, self.reader.parser, self.writer,
+             self.destination))
+        document.transformer.apply_transforms()
+
+    def publish(self, argv=None, usage=None, description=None,
+                settings_spec=None, settings_overrides=None,
+                config_section=None, enable_exit_status=None):
+        """
+        Process command line options and arguments (if `self.settings` not
+        already set), run `self.reader` and then `self.writer`.  Return
+        `self.writer`'s output.
+        """
+        if self.settings is None:
+            self.process_command_line(
+                argv, usage, description, settings_spec, config_section,
+                **(settings_overrides or {}))
+        self.set_io()
+        exit = None
+        document = None
+        try:
+            document = self.reader.read(self.source, self.parser,
+                                        self.settings)
+            self.apply_transforms(document)
+            output = self.writer.write(document, self.destination)
+            self.writer.assemble_parts()
+        except Exception, error:
+            if self.settings.traceback: # propagate exceptions?
+                raise
+            self.report_Exception(error)
+            exit = 1
+        self.debugging_dumps(document)
+        if (enable_exit_status and document
+            and (document.reporter.max_level
+                 >= self.settings.exit_status_level)):
+            sys.exit(document.reporter.max_level + 10)
+        elif exit:
+            sys.exit(1)
+        return output
+
+    def debugging_dumps(self, document):
+        if self.settings.dump_settings:
+            print >>sys.stderr, '\n::: Runtime settings:'
+            print >>sys.stderr, pprint.pformat(self.settings.__dict__)
+        if self.settings.dump_internals and document:
+            print >>sys.stderr, '\n::: Document internals:'
+            print >>sys.stderr, pprint.pformat(document.__dict__)
+        if self.settings.dump_transforms and document:
+            print >>sys.stderr, '\n::: Transforms applied:'
+            print >>sys.stderr, pprint.pformat(document.transformer.applied)
+        if self.settings.dump_pseudo_xml and document:
+            print >>sys.stderr, '\n::: Pseudo-XML:'
+            print >>sys.stderr, document.pformat().encode(
+                'raw_unicode_escape')
+
+    def report_Exception(self, error):
+        if isinstance(error, utils.SystemMessage):
+            self.report_SystemMessage(error)
+        elif isinstance(error, UnicodeError):
+            self.report_UnicodeError(error)
+        else:
+            print >>sys.stderr, '%s: %s' % (error.__class__.__name__, error)
+            print >>sys.stderr, ("""\
+Exiting due to error.  Use "--traceback" to diagnose.
+Please report errors to <docutils-users at lists.sf.net>.
+Include "--traceback" output, Docutils version (%s),
+Python version (%s), your OS type & version, and the
+command line used.""" % (__version__, sys.version.split()[0]))
+
+    def report_SystemMessage(self, error):
+        print >>sys.stderr, ('Exiting due to level-%s (%s) system message.'
+                             % (error.level,
+                                utils.Reporter.levels[error.level]))
+
+    def report_UnicodeError(self, error):
+        sys.stderr.write(
+            '%s: %s\n'
+            '\n'
+            'The specified output encoding (%s) cannot\n'
+            'handle all of the output.\n'
+            'Try setting "--output-encoding-error-handler" to\n'
+            '\n'
+            '* "xmlcharrefreplace" (for HTML & XML output);\n'
+            % (error.__class__.__name__, error,
+               self.settings.output_encoding))
+        try:
+            data = error.object[error.start:error.end]
+            sys.stderr.write(
+                '  the output will contain "%s" and should be usable.\n'
+                '* "backslashreplace" (for other output formats, Python 2.3+);\n'
+                '  look for "%s" in the output.\n'
+                % (data.encode('ascii', 'xmlcharrefreplace'),
+                   data.encode('ascii', 'backslashreplace')))
+        except AttributeError:
+            sys.stderr.write('  the output should be usable as-is.\n')
+        sys.stderr.write(
+            '* "replace"; look for "?" in the output.\n'
+            '\n'
+            '"--output-encoding-error-handler" is currently set to "%s".\n'
+            '\n'
+            'Exiting due to error.  Use "--traceback" to diagnose.\n'
+            'If the advice above doesn\'t eliminate the error,\n'
+            'please report it to <docutils-users at lists.sf.net>.\n'
+            'Include "--traceback" output, Docutils version (%s),\n'
+            'Python version (%s), your OS type & version, and the\n'
+            'command line used.\n'
+            % (self.settings.output_encoding_error_handler,
+               __version__, sys.version.split()[0]))
+
+default_usage = '%prog [options] [<source> [<destination>]]'
+default_description = ('Reads from <source> (default is stdin) and writes to '
+                       '<destination> (default is stdout).')
+
+def publish_cmdline(reader=None, reader_name='standalone',
+                    parser=None, parser_name='restructuredtext',
+                    writer=None, writer_name='pseudoxml',
+                    settings=None, settings_spec=None,
+                    settings_overrides=None, config_section=None,
+                    enable_exit_status=1, argv=None,
+                    usage=default_usage, description=default_description):
+    """
+    Set up & run a `Publisher` for command-line-based file I/O (input and
+    output file paths taken automatically from the command line).  Return the
+    encoded string output also.
+
+    Parameters: see `publish_programmatically` for the remainder.
+
+    - `argv`: Command-line argument list to use instead of ``sys.argv[1:]``.
+    - `usage`: Usage string, output if there's a problem parsing the command
+      line.
+    - `description`: Program description, output for the "--help" option
+      (along with command-line option descriptions).
+    """
+    pub = Publisher(reader, parser, writer, settings=settings)
+    pub.set_components(reader_name, parser_name, writer_name)
+    output = pub.publish(
+        argv, usage, description, settings_spec, settings_overrides,
+        config_section=config_section, enable_exit_status=enable_exit_status)
+    return output
+
+def publish_file(source=None, source_path=None,
+                 destination=None, destination_path=None,
+                 reader=None, reader_name='standalone',
+                 parser=None, parser_name='restructuredtext',
+                 writer=None, writer_name='pseudoxml',
+                 settings=None, settings_spec=None, settings_overrides=None,
+                 config_section=None, enable_exit_status=None):
+    """
+    Set up & run a `Publisher` for programmatic use with file-like I/O.
+    Return the encoded string output also.
+
+    Parameters: see `publish_programmatically`.
+    """
+    output, pub = publish_programmatically(
+        source_class=io.FileInput, source=source, source_path=source_path,
+        destination_class=io.FileOutput,
+        destination=destination, destination_path=destination_path,
+        reader=reader, reader_name=reader_name,
+        parser=parser, parser_name=parser_name,
+        writer=writer, writer_name=writer_name,
+        settings=settings, settings_spec=settings_spec,
+        settings_overrides=settings_overrides,
+        config_section=config_section,
+        enable_exit_status=enable_exit_status)
+    return output
+
+def publish_string(source, source_path=None, destination_path=None,
+                   reader=None, reader_name='standalone',
+                   parser=None, parser_name='restructuredtext',
+                   writer=None, writer_name='pseudoxml',
+                   settings=None, settings_spec=None,
+                   settings_overrides=None, config_section=None,
+                   enable_exit_status=None):
+    """
+    Set up & run a `Publisher` for programmatic use with string I/O.  Return
+    the encoded string or Unicode string output.
+
+    For encoded string output, be sure to set the 'output_encoding' setting to
+    the desired encoding.  Set it to 'unicode' for unencoded Unicode string
+    output.  Here's one way::
+
+        publish_string(..., settings_overrides={'output_encoding': 'unicode'})
+
+    Similarly for Unicode string input (`source`)::
+
+        publish_string(..., settings_overrides={'input_encoding': 'unicode'})
+
+    Parameters: see `publish_programmatically`.
+    """
+    output, pub = publish_programmatically(
+        source_class=io.StringInput, source=source, source_path=source_path,
+        destination_class=io.StringOutput,
+        destination=None, destination_path=destination_path,
+        reader=reader, reader_name=reader_name,
+        parser=parser, parser_name=parser_name,
+        writer=writer, writer_name=writer_name,
+        settings=settings, settings_spec=settings_spec,
+        settings_overrides=settings_overrides,
+        config_section=config_section,
+        enable_exit_status=enable_exit_status)
+    return output
+
+def publish_parts(source, source_path=None, destination_path=None,
+                  reader=None, reader_name='standalone',
+                  parser=None, parser_name='restructuredtext',
+                  writer=None, writer_name='pseudoxml',
+                  settings=None, settings_spec=None,
+                  settings_overrides=None, config_section=None,
+                  enable_exit_status=None):
+    """
+    Set up & run a `Publisher`, and return a dictionary of document parts.
+    Dictionary keys are the names of parts, and values are Unicode strings;
+    encoding is up to the client.  For programmatic use with string I/O.
+
+    For encoded string input, be sure to set the 'input_encoding' setting to
+    the desired encoding.  Set it to 'unicode' for unencoded Unicode string
+    input.  Here's how::
+
+        publish_string(..., settings_overrides={'input_encoding': 'unicode'})
+
+    Parameters: see `publish_programmatically`.
+    """
+    output, pub = publish_programmatically(
+        source_class=io.StringInput, source=source, source_path=source_path,
+        destination_class=io.StringOutput,
+        destination=None, destination_path=destination_path,
+        reader=reader, reader_name=reader_name,
+        parser=parser, parser_name=parser_name,
+        writer=writer, writer_name=writer_name,
+        settings=settings, settings_spec=settings_spec,
+        settings_overrides=settings_overrides,
+        config_section=config_section,
+        enable_exit_status=enable_exit_status)
+    return pub.writer.parts
+
+def publish_programmatically(source_class, source, source_path,
+                            destination_class, destination, destination_path,
+                            reader, reader_name,
+                            parser, parser_name,
+                            writer, writer_name,
+                            settings, settings_spec,
+                            settings_overrides, config_section,
+                            enable_exit_status):
+    """
+    Set up & run a `Publisher` for custom programmatic use.  Return the
+    encoded string output and the Publisher object.
+
+    Applications should not need to call this function directly.  If it does
+    seem to be necessary to call this function directly, please write to the
+    docutils-develop at lists.sourceforge.net mailing list.
+
+    Parameters:
+
+    * `source_class` **required**: The class for dynamically created source
+      objects.  Typically `io.FileInput` or `io.StringInput`.
+
+    * `source`: Type depends on `source_class`:
+
+      - `io.FileInput`: Either a file-like object (must have 'read' and
+        'close' methods), or ``None`` (`source_path` is opened).  If neither
+        `source` nor `source_path` are supplied, `sys.stdin` is used.
+
+      - `io.StringInput` **required**: The input string, either an encoded
+        8-bit string (set the 'input_encoding' setting to the correct
+        encoding) or a Unicode string (set the 'input_encoding' setting to
+        'unicode').
+
+    * `source_path`: Type depends on `source_class`:
+
+      - `io.FileInput`: Path to the input file, opened if no `source`
+        supplied.
+
+      - `io.StringInput`: Optional.  Path to the file or object that produced
+        `source`.  Only used for diagnostic output.
+
+    * `destination_class` **required**: The class for dynamically created
+      destination objects.  Typically `io.FileOutput` or `io.StringOutput`.
+
+    * `destination`: Type depends on `destination_class`:
+
+      - `io.FileOutput`: Either a file-like object (must have 'write' and
+        'close' methods), or ``None`` (`destination_path` is opened).  If
+        neither `destination` nor `destination_path` are supplied,
+        `sys.stdout` is used.
+
+      - `io.StringOutput`: Not used; pass ``None``.
+
+    * `destination_path`: Type depends on `destination_class`:
+
+      - `io.FileOutput`: Path to the output file.  Opened if no `destination`
+        supplied.
+
+      - `io.StringOutput`: Path to the file or object which will receive the
+        output; optional.  Used for determining relative paths (stylesheets,
+        source links, etc.).
+
+    * `reader`: A `docutils.readers.Reader` object.
+
+    * `reader_name`: Name or alias of the Reader class to be instantiated if
+      no `reader` supplied.
+
+    * `parser`: A `docutils.parsers.Parser` object.
+
+    * `parser_name`: Name or alias of the Parser class to be instantiated if
+      no `parser` supplied.
+
+    * `writer`: A `docutils.writers.Writer` object.
+
+    * `writer_name`: Name or alias of the Writer class to be instantiated if
+      no `writer` supplied.
+
+    * `settings`: A runtime settings (`docutils.frontend.Values`) object, for
+      dotted-attribute access to runtime settings.  It's the end result of the
+      `SettingsSpec`, config file, and option processing.  If `settings` is
+      passed, it's assumed to be complete and no further setting/config/option
+      processing is done.
+
+    * `settings_spec`: A `docutils.SettingsSpec` subclass or object.  Provides
+      extra application-specific settings definitions independently of
+      components.  In other words, the application becomes a component, and
+      its settings data is processed along with that of the other components.
+      Used only if no `settings` specified.
+
+    * `settings_overrides`: A dictionary containing application-specific
+      settings defaults that override the defaults of other components.
+      Used only if no `settings` specified.
+
+    * `config_section`: A string, the name of the configuration file section
+      for this application.  Overrides the ``config_section`` attribute
+      defined by `settings_spec`.  Used only if no `settings` specified.
+
+    * `enable_exit_status`: Boolean; enable exit status at end of processing?
+    """
+    pub = Publisher(reader, parser, writer, settings=settings,
+                    source_class=source_class,
+                    destination_class=destination_class)
+    pub.set_components(reader_name, parser_name, writer_name)
+    pub.process_programmatic_settings(
+        settings_spec, settings_overrides, config_section)
+    pub.set_source(source, source_path)
+    pub.set_destination(destination, destination_path)
+    output = pub.publish(enable_exit_status=enable_exit_status)
+    return output, pub

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/examples.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/examples.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/examples.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,74 @@
+# Authors: David Goodger
+# Contact: goodger at python.org
+# Revision: $Revision: 1.1.4.3 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This module contains practical examples of Docutils client code.
+
+Importing this module is not recommended; its contents are subject to change
+in future Docutils releases.  Instead, it is recommended that you copy and
+paste the parts you need into your own code, modifying as necessary.
+"""
+
+from docutils import core
+
+
+def html_parts(input_string, source_path=None, destination_path=None,
+               input_encoding='unicode', doctitle=1, initial_header_level=1):
+    """
+    Given an input string, returns a dictionary of HTML document parts.
+
+    Dictionary keys are the names of parts, and values are Unicode strings;
+    encoding is up to the client.
+
+    Parameters:
+
+    - `input_string`: A multi-line text string; required.
+    - `source_path`: Path to the source file or object.  Optional, but useful
+      for diagnostic output (system messages).
+    - `destination_path`: Path to the file or object which will receive the
+      output; optional.  Used for determining relative paths (stylesheets,
+      source links, etc.).
+    - `input_encoding`: The encoding of `input_string`.  If it is an encoded
+      8-bit string, provide the correct encoding.  If it is a Unicode string,
+      use "unicode", the default.
+    - `doctitle`: Disable the promotion of a lone top-level section title to
+      document title (and subsequent section title to document subtitle
+      promotion); enabled by default.
+    - `initial_header_level`: The initial level for header elements (e.g. 1
+      for "<h1>").
+    """
+    overrides = {'input_encoding': input_encoding,
+                 'doctitle_xform': doctitle,
+                 'initial_header_level': initial_header_level}
+    parts = core.publish_parts(
+        source=input_string, source_path=source_path,
+        destination_path=destination_path,
+        writer_name='html', settings_overrides=overrides)
+    return parts
+
+def html_fragment(input_string, source_path=None, destination_path=None,
+                  input_encoding='unicode', output_encoding='unicode',
+                  doctitle=1, initial_header_level=1):
+    """
+    Given an input string, returns an HTML fragment as a string.
+
+    The return value is the contents of the <body> tag, less the title,
+    subtitle, and docinfo.
+
+    Parameters (see `html_parts()` for the remainder):
+
+    - `output_encoding`: The desired encoding of the output.  If a Unicode
+      string is desired, use the default value of "unicode" .
+    """
+    parts = html_parts(
+        input_string=input_string, source_path=source_path,
+        destination_path=destination_path,
+        input_encoding=input_encoding, doctitle=doctitle,
+        initial_header_level=initial_header_level)
+    fragment = parts['fragment']
+    if output_encoding != 'unicode':
+        fragment = fragment.encode(output_encoding)
+    return fragment

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/frontend.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/frontend.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/frontend.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,686 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.8 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Command-line and common processing for Docutils front-end tools.
+
+Exports the following classes:
+
+* `OptionParser`: Standard Docutils command-line processing.
+* `Option`: Customized version of `optparse.Option`; validation support.
+* `Values`: Runtime settings; objects are simple structs
+  (``object.attribute``).  Supports cumulative list settings (attributes).
+* `ConfigParser`: Standard Docutils config file processing.
+
+Also exports the following functions:
+
+* Option callbacks: `store_multiple`, `read_config_file`.
+* Setting validators: `validate_encoding`,
+  `validate_encoding_error_handler`,
+  `validate_encoding_and_error_handler`, `validate_boolean`,
+  `validate_threshold`, `validate_colon_separated_string_list`,
+  `validate_dependency_file`.
+* `make_paths_absolute`.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import os
+import os.path
+import sys
+import types
+import copy
+import warnings
+import ConfigParser as CP
+import codecs
+import docutils
+import optparse
+from optparse import SUPPRESS_HELP
+
+
+def store_multiple(option, opt, value, parser, *args, **kwargs):
+    """
+    Store multiple values in `parser.values`.  (Option callback.)
+
+    Store `None` for each attribute named in `args`, and store the value for
+    each key (attribute name) in `kwargs`.
+    """
+    for attribute in args:
+        setattr(parser.values, attribute, None)
+    for key, value in kwargs.items():
+        setattr(parser.values, key, value)
+
+def read_config_file(option, opt, value, parser):
+    """
+    Read a configuration file during option processing.  (Option callback.)
+    """
+    try:
+        new_settings = parser.get_config_file_settings(value)
+    except ValueError, error:
+        parser.error(error)
+    parser.values.update(new_settings, parser)
+
+def validate_encoding(setting, value, option_parser,
+                      config_parser=None, config_section=None):
+    try:
+        codecs.lookup(value)
+    except LookupError:
+        raise (LookupError('setting "%s": unknown encoding: "%s"'
+                           % (setting, value)),
+               None, sys.exc_info()[2])
+    return value
+
+def validate_encoding_error_handler(setting, value, option_parser,
+                                    config_parser=None, config_section=None):
+    try:
+        codecs.lookup_error(value)
+    except AttributeError:              # prior to Python 2.3
+        if value not in ('strict', 'ignore', 'replace', 'xmlcharrefreplace'):
+            raise (LookupError(
+                'unknown encoding error handler: "%s" (choices: '
+                '"strict", "ignore", "replace", or "xmlcharrefreplace")' % value),
+                   None, sys.exc_info()[2])
+    except LookupError:
+        raise (LookupError(
+            'unknown encoding error handler: "%s" (choices: '
+            '"strict", "ignore", "replace", "backslashreplace", '
+            '"xmlcharrefreplace", and possibly others; see documentation for '
+            'the Python ``codecs`` module)' % value),
+               None, sys.exc_info()[2])
+    return value
+
+def validate_encoding_and_error_handler(
+    setting, value, option_parser, config_parser=None, config_section=None):
+    """
+    Side-effect: if an error handler is included in the value, it is inserted
+    into the appropriate place as if it was a separate setting/option.
+    """
+    if ':' in value:
+        encoding, handler = value.split(':')
+        validate_encoding_error_handler(
+            setting + '_error_handler', handler, option_parser,
+            config_parser, config_section)
+        if config_parser:
+            config_parser.set(config_section, setting + '_error_handler',
+                              handler)
+        else:
+            setattr(option_parser.values, setting + '_error_handler', handler)
+    else:
+        encoding = value
+    validate_encoding(setting, encoding, option_parser,
+                      config_parser, config_section)
+    return encoding
+
+def validate_boolean(setting, value, option_parser,
+                     config_parser=None, config_section=None):
+    if isinstance(value, types.StringType):
+        try:
+            return option_parser.booleans[value.strip().lower()]
+        except KeyError:
+            raise (LookupError('unknown boolean value: "%s"' % value),
+                   None, sys.exc_info()[2])
+    return value
+
+def validate_threshold(setting, value, option_parser,
+                       config_parser=None, config_section=None):
+    try:
+        return int(value)
+    except ValueError:
+        try:
+            return option_parser.thresholds[value.lower()]
+        except (KeyError, AttributeError):
+            raise (LookupError('unknown threshold: %r.' % value),
+                   None, sys.exc_info[2])
+
+def validate_colon_separated_string_list(
+    setting, value, option_parser, config_parser=None, config_section=None):
+    if isinstance(value, types.StringType):
+        value = value.split(':')
+    else:
+        last = value.pop()
+        value.extend(last.split(':'))
+    return value
+
+def validate_url_trailing_slash(
+    setting, value, option_parser, config_parser=None, config_section=None):
+    if not value:
+        return './'
+    elif value.endswith('/'):
+        return value
+    else:
+        return value + '/'
+
+def validate_dependency_file(
+    setting, value, option_parser, config_parser=None, config_section=None):
+    try:
+        return docutils.utils.DependencyList(value)
+    except IOError:
+        return docutils.utils.DependencyList(None)
+
+def make_paths_absolute(pathdict, keys, base_path=None):
+    """
+    Interpret filesystem path settings relative to the `base_path` given.
+
+    Paths are values in `pathdict` whose keys are in `keys`.  Get `keys` from
+    `OptionParser.relative_path_settings`.
+    """
+    if base_path is None:
+        base_path = os.getcwd()
+    for key in keys:
+        if pathdict.has_key(key):
+            value = pathdict[key]
+            if isinstance(value, types.ListType):
+                value = [make_one_path_absolute(base_path, path)
+                         for path in value]
+            elif value:
+                value = make_one_path_absolute(base_path, value)
+            pathdict[key] = value
+
+def make_one_path_absolute(base_path, path):
+    return os.path.abspath(os.path.join(base_path, path))
+
+
+class Values(optparse.Values):
+
+    """
+    Updates list attributes by extension rather than by replacement.
+    Works in conjunction with the `OptionParser.lists` instance attribute.
+    """
+
+    def __init__(self, *args, **kwargs):
+        optparse.Values.__init__(self, *args, **kwargs)
+        if (not hasattr(self, 'record_dependencies')
+            or self.record_dependencies is None):
+            # Set up dependency list, in case it is needed.
+            self.record_dependencies = docutils.utils.DependencyList()
+
+    def update(self, other_dict, option_parser):
+        if isinstance(other_dict, Values):
+            other_dict = other_dict.__dict__
+        other_dict = other_dict.copy()
+        for setting in option_parser.lists.keys():
+            if (hasattr(self, setting) and other_dict.has_key(setting)):
+                value = getattr(self, setting)
+                if value:
+                    value += other_dict[setting]
+                    del other_dict[setting]
+        self._update_loose(other_dict)
+
+
+class Option(optparse.Option):
+
+    ATTRS = optparse.Option.ATTRS + ['validator', 'overrides']
+
+    def process(self, opt, value, values, parser):
+        """
+        Call the validator function on applicable settings and
+        evaluate the 'overrides' option.
+        Extends `optparse.Option.process`.
+        """
+        result = optparse.Option.process(self, opt, value, values, parser)
+        setting = self.dest
+        if setting:
+            if self.validator:
+                value = getattr(values, setting)
+                try:
+                    new_value = self.validator(setting, value, parser)
+                except Exception, error:
+                    raise (optparse.OptionValueError(
+                        'Error in option "%s":\n    %s: %s'
+                        % (opt, error.__class__.__name__, error)),
+                           None, sys.exc_info()[2])
+                setattr(values, setting, new_value)
+            if self.overrides:
+                setattr(values, self.overrides, None)
+        return result
+
+
+class OptionParser(optparse.OptionParser, docutils.SettingsSpec):
+
+    """
+    Parser for command-line and library use.  The `settings_spec`
+    specification here and in other Docutils components are merged to build
+    the set of command-line options and runtime settings for this process.
+
+    Common settings (defined below) and component-specific settings must not
+    conflict.  Short options are reserved for common settings, and components
+    are restrict to using long options.
+    """
+
+    standard_config_files = [
+        '/etc/docutils.conf',           # system-wide
+        './docutils.conf',              # project-specific
+        '~/.docutils']                  # user-specific
+    """Docutils configuration files, using ConfigParser syntax.  Filenames
+    will be tilde-expanded later.  Later files override earlier ones."""
+
+    threshold_choices = 'info 1 warning 2 error 3 severe 4 none 5'.split()
+    """Possible inputs for for --report and --halt threshold values."""
+
+    thresholds = {'info': 1, 'warning': 2, 'error': 3, 'severe': 4, 'none': 5}
+    """Lookup table for --report and --halt threshold values."""
+
+    booleans={'1': 1, 'on': 1, 'yes': 1, 'true': 1,
+              '0': 0, 'off': 0, 'no': 0, 'false': 0, '': 0}
+    """Lookup table for boolean configuration file settings."""
+
+    if hasattr(codecs, 'backslashreplace_errors'):
+        default_error_encoding_error_handler = 'backslashreplace'
+    else:
+        default_error_encoding_error_handler = 'replace'
+
+    settings_spec = (
+        'General Docutils Options',
+        None,
+        (('Include a "Generated by Docutils" credit and link at the end '
+          'of the document.',
+          ['--generator', '-g'], {'action': 'store_true',
+                                  'validator': validate_boolean}),
+         ('Do not include a generator credit.',
+          ['--no-generator'], {'action': 'store_false', 'dest': 'generator'}),
+         ('Include the date at the end of the document (UTC).',
+          ['--date', '-d'], {'action': 'store_const', 'const': '%Y-%m-%d',
+                             'dest': 'datestamp'}),
+         ('Include the time & date at the end of the document (UTC).',
+          ['--time', '-t'], {'action': 'store_const',
+                             'const': '%Y-%m-%d %H:%M UTC',
+                             'dest': 'datestamp'}),
+         ('Do not include a datestamp of any kind.',
+          ['--no-datestamp'], {'action': 'store_const', 'const': None,
+                               'dest': 'datestamp'}),
+         ('Include a "View document source" link (relative to destination).',
+          ['--source-link', '-s'], {'action': 'store_true',
+                                    'validator': validate_boolean}),
+         ('Use the supplied <URL> verbatim for a "View document source" '
+          'link; implies --source-link.',
+          ['--source-url'], {'metavar': '<URL>'}),
+         ('Do not include a "View document source" link.',
+          ['--no-source-link'],
+          {'action': 'callback', 'callback': store_multiple,
+           'callback_args': ('source_link', 'source_url')}),
+         ('Enable backlinks from section headers to table of contents '
+          'entries.  This is the default.',
+          ['--toc-entry-backlinks'],
+          {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'entry',
+           'default': 'entry'}),
+         ('Enable backlinks from section headers to the top of the table of '
+          'contents.',
+          ['--toc-top-backlinks'],
+          {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'top'}),
+         ('Disable backlinks to the table of contents.',
+          ['--no-toc-backlinks'],
+          {'dest': 'toc_backlinks', 'action': 'store_false'}),
+         ('Enable backlinks from footnotes and citations to their '
+          'references.  This is the default.',
+          ['--footnote-backlinks'],
+          {'action': 'store_true', 'default': 1,
+           'validator': validate_boolean}),
+         ('Disable backlinks from footnotes and citations.',
+          ['--no-footnote-backlinks'],
+          {'dest': 'footnote_backlinks', 'action': 'store_false'}),
+         ('Disable Docutils section numbering',
+          ['--no-section-numbering'],
+          {'action': 'store_false', 'dest': 'sectnum_xform',
+           'default': 1, 'validator': validate_boolean}),
+         ('Set verbosity threshold; report system messages at or higher than '
+          '<level> (by name or number: "info" or "1", warning/2, error/3, '
+          'severe/4; also, "none" or "5").  Default is 2 (warning).',
+          ['--report', '-r'], {'choices': threshold_choices, 'default': 2,
+                               'dest': 'report_level', 'metavar': '<level>',
+                               'validator': validate_threshold}),
+         ('Report all system messages, info-level and higher.  (Same as '
+          '"--report=info".)',
+          ['--verbose', '-v'], {'action': 'store_const', 'const': 'info',
+                                'dest': 'report_level'}),
+         ('Do not report any system messages.  (Same as "--report=none".)',
+          ['--quiet', '-q'], {'action': 'store_const', 'const': 'none',
+                              'dest': 'report_level'}),
+         ('Set the threshold (<level>) at or above which system messages are '
+          'converted to exceptions, halting execution immediately by '
+          'exiting (or propagating the exception if --traceback set).  '
+          'Levels as in --report.  Default is 4 (severe).',
+          ['--halt'], {'choices': threshold_choices, 'dest': 'halt_level',
+                       'default': 4, 'metavar': '<level>',
+                       'validator': validate_threshold}),
+         ('Same as "--halt=info": halt processing at the slightest problem.',
+          ['--strict'], {'action': 'store_const', 'const': 'info',
+                         'dest': 'halt_level'}),
+         ('Enable a non-zero exit status for normal exit if non-halting '
+          'system messages (at or above <level>) were generated.  Levels as '
+          'in --report.  Default is 5 (disabled).  Exit status is the maximum '
+          'system message level plus 10 (11 for INFO, etc.).',
+          ['--exit-status'], {'choices': threshold_choices,
+                              'dest': 'exit_status_level',
+                              'default': 5, 'metavar': '<level>',
+                              'validator': validate_threshold}),
+         ('Report debug-level system messages and generate diagnostic output.',
+          ['--debug'], {'action': 'store_true', 'validator': validate_boolean}),
+         ('Do not report debug-level system messages or generate diagnostic '
+          'output.',
+          ['--no-debug'], {'action': 'store_false', 'dest': 'debug'}),
+         ('Send the output of system messages (warnings) to <file>.',
+          ['--warnings'], {'dest': 'warning_stream', 'metavar': '<file>'}),
+         ('Enable Python tracebacks when halt-level system messages and '
+          'other exceptions occur.  Useful for debugging, and essential for '
+          'issue reports.',
+          ['--traceback'], {'action': 'store_true', 'default': None,
+                            'validator': validate_boolean}),
+         ('Disable Python tracebacks when errors occur; report just the error '
+          'instead.  This is the default.',
+          ['--no-traceback'], {'dest': 'traceback', 'action': 'store_false'}),
+         ('Specify the encoding of input text.  Default is locale-dependent.  '
+          'Optionally also specify the error handler for undecodable '
+          'characters, after a colon (":"); default is "strict".  (See '
+          '"--intput-encoding-error-handler".)',
+          ['--input-encoding', '-i'],
+          {'metavar': '<name[:handler]>',
+           'validator': validate_encoding_and_error_handler}),
+         ('Specify the error handler for undecodable characters in '
+          'the input.  Acceptable values include "strict", "ignore", and '
+          '"replace".  Default is "strict".  '
+          'Usually specified as part of --input-encoding.',
+          ['--input-encoding-error-handler'],
+          {'default': 'strict', 'validator': validate_encoding_error_handler}),
+         ('Specify the text encoding for output.  Default is UTF-8.  '
+          'Optionally also specify the error handler for unencodable '
+          'characters, after a colon (":"); default is "strict".  (See '
+          '"--output-encoding-error-handler".)',
+          ['--output-encoding', '-o'],
+          {'metavar': '<name[:handler]>', 'default': 'utf-8',
+           'validator': validate_encoding_and_error_handler}),
+         ('Specify the error handler for unencodable characters in '
+          'the output.  Acceptable values include "strict", "ignore", '
+          '"replace", "xmlcharrefreplace", and '
+          '"backslashreplace" (in Python 2.3+).  Default is "strict".  '
+          'Usually specified as part of --output-encoding.',
+          ['--output-encoding-error-handler'],
+          {'default': 'strict', 'validator': validate_encoding_error_handler}),
+         ('Specify the text encoding for error output.  Default is ASCII.  '
+          'Optionally also specify the error handler for unencodable '
+          'characters, after a colon (":"); default is "%s".  (See '
+          '"--output-encoding-error-handler".)'
+          % default_error_encoding_error_handler,
+          ['--error-encoding', '-e'],
+          {'metavar': '<name[:handler]>', 'default': 'ascii',
+           'validator': validate_encoding_and_error_handler}),
+         ('Specify the error handler for unencodable characters in '
+          'error output.  See --output-encoding-error-handler for acceptable '
+          'values.  Default is "%s".  Usually specified as part of '
+          '--error-encoding.' % default_error_encoding_error_handler,
+          ['--error-encoding-error-handler'],
+          {'default': default_error_encoding_error_handler,
+           'validator': validate_encoding_error_handler}),
+         ('Specify the language of input text (ISO 639 2-letter identifier).'
+          '  Default is "en" (English).',
+          ['--language', '-l'], {'dest': 'language_code', 'default': 'en',
+                                 'metavar': '<name>'}),
+         ('Write dependencies (caused e.g. by file inclusions) to '
+          '<file>.  Useful in conjunction with programs like "make".',
+          ['--record-dependencies'],
+          {'metavar': '<file>', 'validator': validate_dependency_file,
+           'default': None}),           # default set in Values class
+         ('Read configuration settings from <file>, if it exists.',
+          ['--config'], {'metavar': '<file>', 'type': 'string',
+                         'action': 'callback', 'callback': read_config_file}),
+         ("Show this program's version number and exit.",
+          ['--version', '-V'], {'action': 'version'}),
+         ('Show this help message and exit.',
+          ['--help', '-h'], {'action': 'help'}),
+         # Hidden options, for development use only:
+         (SUPPRESS_HELP, ['--dump-settings'], {'action': 'store_true'}),
+         (SUPPRESS_HELP, ['--dump-internals'], {'action': 'store_true'}),
+         (SUPPRESS_HELP, ['--dump-transforms'], {'action': 'store_true'}),
+         (SUPPRESS_HELP, ['--dump-pseudo-xml'], {'action': 'store_true'}),
+         (SUPPRESS_HELP, ['--expose-internal-attribute'],
+          {'action': 'append', 'dest': 'expose_internals',
+           'validator': validate_colon_separated_string_list}),
+         (SUPPRESS_HELP, ['--strict-visitor'], {'action': 'store_true'}),
+         ))
+    """Runtime settings and command-line options common to all Docutils front
+    ends.  Setting specs specific to individual Docutils components are also
+    used (see `populate_from_components()`)."""
+
+    settings_defaults = {'_disable_config': None,
+                         '_source': None,
+                         '_destination': None}
+    """Defaults for settings that don't have command-line option equivalents."""
+
+    relative_path_settings = ('warning_stream',)
+
+    config_section = 'general'
+
+    version_template = '%%prog (Docutils %s)' % docutils.__version__
+    """Default version message."""
+
+    def __init__(self, components=(), defaults=None, read_config_files=None,
+                 *args, **kwargs):
+        """
+        `components` is a list of Docutils components each containing a
+        ``.settings_spec`` attribute.  `defaults` is a mapping of setting
+        default overrides.
+        """
+
+        self.lists = {}
+        """Set of list-type settings."""
+
+        optparse.OptionParser.__init__(
+            self, option_class=Option, add_help_option=None,
+            formatter=optparse.TitledHelpFormatter(width=78),
+            *args, **kwargs)
+        if not self.version:
+            self.version = self.version_template
+        # Make an instance copy (it will be modified):
+        self.relative_path_settings = list(self.relative_path_settings)
+        self.components = (self,) + tuple(components)
+        self.populate_from_components(self.components)
+        self.set_defaults(**(defaults or {}))
+        if read_config_files and not self.defaults['_disable_config']:
+            try:
+                config_settings = self.get_standard_config_settings()
+            except ValueError, error:
+                self.error(error)
+            self.set_defaults(**config_settings.__dict__)
+
+    def populate_from_components(self, components):
+        """
+        For each component, first populate from the `SettingsSpec.settings_spec`
+        structure, then from the `SettingsSpec.settings_defaults` dictionary.
+        After all components have been processed, check for and populate from
+        each component's `SettingsSpec.settings_default_overrides` dictionary.
+        """
+        for component in components:
+            if component is None:
+                continue
+            settings_spec = component.settings_spec
+            self.relative_path_settings.extend(
+                component.relative_path_settings)
+            for i in range(0, len(settings_spec), 3):
+                title, description, option_spec = settings_spec[i:i+3]
+                if title:
+                    group = optparse.OptionGroup(self, title, description)
+                    self.add_option_group(group)
+                else:
+                    group = self        # single options
+                for (help_text, option_strings, kwargs) in option_spec:
+                    option = group.add_option(help=help_text, *option_strings,
+                                              **kwargs)
+                    if kwargs.get('action') == 'append':
+                        self.lists[option.dest] = 1
+                if component.settings_defaults:
+                    self.defaults.update(component.settings_defaults)
+        for component in components:
+            if component and component.settings_default_overrides:
+                self.defaults.update(component.settings_default_overrides)
+
+    def get_standard_config_files(self):
+        """Return list of config files, from environment or standard."""
+        try:
+            config_files = os.environ['DOCUTILSCONFIG'].split(os.pathsep)
+        except KeyError:
+            config_files = self.standard_config_files
+        return [os.path.expanduser(f) for f in config_files if f.strip()]
+
+    def get_standard_config_settings(self):
+        settings = Values()
+        for filename in self.get_standard_config_files():
+            settings.update(self.get_config_file_settings(filename), self)
+        return settings
+
+    def get_config_file_settings(self, config_file):
+        """Returns a dictionary containing appropriate config file settings."""
+        parser = ConfigParser()
+        parser.read(config_file, self)
+        base_path = os.path.dirname(config_file)
+        applied = {}
+        settings = Values()
+        for component in self.components:
+            if not component:
+                continue
+            for section in (tuple(component.config_section_dependencies or ())
+                            + (component.config_section,)):
+                if applied.has_key(section):
+                    continue
+                applied[section] = 1
+                settings.update(parser.get_section(section), self)
+        make_paths_absolute(
+            settings.__dict__, self.relative_path_settings, base_path)
+        return settings.__dict__
+
+    def check_values(self, values, args):
+        """Store positional arguments as runtime settings."""
+        values._source, values._destination = self.check_args(args)
+        make_paths_absolute(values.__dict__, self.relative_path_settings,
+                            os.getcwd())
+        return values
+
+    def check_args(self, args):
+        source = destination = None
+        if args:
+            source = args.pop(0)
+            if source == '-':           # means stdin
+                source = None
+        if args:
+            destination = args.pop(0)
+            if destination == '-':      # means stdout
+                destination = None
+        if args:
+            self.error('Maximum 2 arguments allowed.')
+        if source and source == destination:
+            self.error('Do not specify the same file for both source and '
+                       'destination.  It will clobber the source file.')
+        return source, destination
+
+    def get_default_values(self):
+        """Needed to get custom `Values` instances."""
+        return Values(self.defaults)
+
+    def get_option_by_dest(self, dest):
+        """
+        Get an option by its dest.
+
+        If you're supplying a dest which is shared by several options,
+        it is undefined which option of those is returned.
+
+        A KeyError is raised if there is no option with the supplied
+        dest.
+        """
+        for group in self.option_groups + [self]:
+            for option in group.option_list:
+                if option.dest == dest:
+                    return option
+        raise KeyError('No option with dest == %r.' % dest)
+
+
+class ConfigParser(CP.ConfigParser):
+
+    old_settings = {
+        'pep_stylesheet': ('pep_html writer', 'stylesheet'),
+        'pep_stylesheet_path': ('pep_html writer', 'stylesheet_path'),
+        'pep_template': ('pep_html writer', 'template')}
+    """{old setting: (new section, new setting)} mapping, used by
+    `handle_old_config`, to convert settings from the old [options] section."""
+
+    old_warning = """
+The "[option]" section is deprecated.  Support for old-format configuration
+files may be removed in a future Docutils release.  Please revise your
+configuration files.  See <http://docutils.sf.net/docs/user/config.html>,
+section "Old-Format Configuration Files".
+"""
+
+    def read(self, filenames, option_parser):
+        if type(filenames) in (types.StringType, types.UnicodeType):
+            filenames = [filenames]
+        for filename in filenames:
+            CP.ConfigParser.read(self, filename)
+            if self.has_section('options'):
+                self.handle_old_config(filename)
+            self.validate_settings(filename, option_parser)
+
+    def handle_old_config(self, filename):
+        warnings.warn_explicit(self.old_warning, ConfigDeprecationWarning,
+                               filename, 0)
+        options = self.get_section('options')
+        if not self.has_section('general'):
+            self.add_section('general')
+        for key, value in options.items():
+            if self.old_settings.has_key(key):
+                section, setting = self.old_settings[key]
+                if not self.has_section(section):
+                    self.add_section(section)
+            else:
+                section = 'general'
+                setting = key
+            if not self.has_option(section, setting):
+                self.set(section, setting, value)
+        self.remove_section('options')
+
+    def validate_settings(self, filename, option_parser):
+        """
+        Call the validator function and implement overrides on all applicable
+        settings.
+        """
+        for section in self.sections():
+            for setting in self.options(section):
+                try:
+                    option = option_parser.get_option_by_dest(setting)
+                except KeyError:
+                    continue
+                if option.validator:
+                    value = self.get(section, setting, raw=1)
+                    try:
+                        new_value = option.validator(
+                            setting, value, option_parser,
+                            config_parser=self, config_section=section)
+                    except Exception, error:
+                        raise (ValueError(
+                            'Error in config file "%s", section "[%s]":\n'
+                            '    %s: %s\n        %s = %s'
+                            % (filename, section, error.__class__.__name__,
+                               error, setting, value)), None, sys.exc_info()[2])
+                    self.set(section, setting, new_value)
+                if option.overrides:
+                    self.set(section, option.overrides, None)
+
+    def optionxform(self, optionstr):
+        """
+        Transform '-' to '_' so the cmdline form of option names can be used.
+        """
+        return optionstr.lower().replace('-', '_')
+
+    def get_section(self, section):
+        """
+        Return a given section as a dictionary (empty if the section
+        doesn't exist).
+        """
+        section_dict = {}
+        if self.has_section(section):
+            for option in self.options(section):
+                section_dict[option] = self.get(section, option, raw=1)
+        return section_dict
+
+
+class ConfigDeprecationWarning(DeprecationWarning):
+    """Warning for deprecated configuration file features."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/io.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/io.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/io.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,337 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+I/O classes provide a uniform API for low-level input and output.  Subclasses
+will exist for a variety of input/output mechanisms.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+try:
+    import locale
+except:
+    pass
+from types import UnicodeType
+from docutils import TransformSpec
+
+
+class Input(TransformSpec):
+
+    """
+    Abstract base class for input wrappers.
+    """
+
+    component_type = 'input'
+
+    default_source_path = None
+
+    def __init__(self, source=None, source_path=None, encoding=None,
+                 error_handler='strict'):
+        self.encoding = encoding
+        """Text encoding for the input source."""
+
+        self.error_handler = error_handler
+        """Text decoding error handler."""
+
+        self.source = source
+        """The source of input data."""
+
+        self.source_path = source_path
+        """A text reference to the source."""
+
+        if not source_path:
+            self.source_path = self.default_source_path
+
+        self.successful_encoding = None
+        """The encoding that successfully decoded the source data."""
+
+    def __repr__(self):
+        return '%s: source=%r, source_path=%r' % (self.__class__, self.source,
+                                                  self.source_path)
+
+    def read(self):
+        raise NotImplementedError
+
+    def decode(self, data):
+        """
+        Decode a string, `data`, heuristically.
+        Raise UnicodeError if unsuccessful.
+
+        The client application should call ``locale.setlocale`` at the
+        beginning of processing::
+
+            locale.setlocale(locale.LC_ALL, '')
+        """
+        if (self.encoding and self.encoding.lower() == 'unicode'
+            or isinstance(data, UnicodeType)):
+            return data
+        encodings = [self.encoding, 'utf-8']
+        try:
+            encodings.append(locale.nl_langinfo(locale.CODESET))
+        except:
+            pass
+        try:
+            encodings.append(locale.getlocale()[1])
+        except:
+            pass
+        try:
+            encodings.append(locale.getdefaultlocale()[1])
+        except:
+            pass
+        encodings.append('latin-1')
+        for enc in encodings:
+            if not enc:
+                continue
+            try:
+                decoded = unicode(data, enc, self.error_handler)
+                self.successful_encoding = enc
+                return decoded
+            except (UnicodeError, LookupError):
+                pass
+        raise UnicodeError(
+            'Unable to decode input data.  Tried the following encodings: %s.'
+            % ', '.join([repr(enc) for enc in encodings if enc]))
+
+
+class Output(TransformSpec):
+
+    """
+    Abstract base class for output wrappers.
+    """
+
+    component_type = 'output'
+
+    default_destination_path = None
+
+    def __init__(self, destination=None, destination_path=None,
+                 encoding=None, error_handler='strict'):
+        self.encoding = encoding
+        """Text encoding for the output destination."""
+
+        self.error_handler = error_handler or 'strict'
+        """Text encoding error handler."""
+
+        self.destination = destination
+        """The destination for output data."""
+
+        self.destination_path = destination_path
+        """A text reference to the destination."""
+
+        if not destination_path:
+            self.destination_path = self.default_destination_path
+
+    def __repr__(self):
+        return ('%s: destination=%r, destination_path=%r'
+                % (self.__class__, self.destination, self.destination_path))
+
+    def write(self, data):
+        """`data` is a Unicode string, to be encoded by `self.encode`."""
+        raise NotImplementedError
+
+    def encode(self, data):
+        if self.encoding and self.encoding.lower() == 'unicode':
+            return data
+        else:
+            try:
+                return data.encode(self.encoding, self.error_handler)
+            except ValueError:
+                # ValueError is raised if there are unencodable chars
+                # in data and the error_handler isn't found.
+                if self.error_handler == 'xmlcharrefreplace':
+                    # We are using xmlcharrefreplace with a Python
+                    # version that doesn't support it (2.1 or 2.2), so
+                    # we emulate its behavior.
+                    return ''.join([self.xmlcharref_encode(char) for char in data])
+                else:
+                    raise
+
+    def xmlcharref_encode(self, char):
+        """Emulate Python 2.3's 'xmlcharrefreplace' encoding error handler."""
+        try:
+            return char.encode(self.encoding, 'strict')
+        except UnicodeError:
+            return '&#%i;' % ord(char)
+
+
+class FileInput(Input):
+
+    """
+    Input for single, simple file-like objects.
+    """
+
+    def __init__(self, source=None, source_path=None,
+                 encoding=None, error_handler='strict',
+                 autoclose=1, handle_io_errors=1):
+        """
+        :Parameters:
+            - `source`: either a file-like object (which is read directly), or
+              `None` (which implies `sys.stdin` if no `source_path` given).
+            - `source_path`: a path to a file, which is opened and then read.
+            - `encoding`: the expected text encoding of the input file.
+            - `error_handler`: the encoding error handler to use.
+            - `autoclose`: close automatically after read (boolean); always
+              false if `sys.stdin` is the source.
+            - `handle_io_errors`: summarize I/O errors here, and exit?
+        """
+        Input.__init__(self, source, source_path, encoding, error_handler)
+        self.autoclose = autoclose
+        self.handle_io_errors = handle_io_errors
+        if source is None:
+            if source_path:
+                try:
+                    self.source = open(source_path)
+                except IOError, error:
+                    if not handle_io_errors:
+                        raise
+                    print >>sys.stderr, '%s: %s' % (error.__class__.__name__,
+                                                    error)
+                    print >>sys.stderr, (
+                        'Unable to open source file for reading (%r).  Exiting.'
+                        % source_path)
+                    sys.exit(1)
+            else:
+                self.source = sys.stdin
+                self.autoclose = None
+        if not source_path:
+            try:
+                self.source_path = self.source.name
+            except AttributeError:
+                pass
+
+    def read(self):
+        """
+        Read and decode a single file and return the data (Unicode string).
+        """
+        try:
+            data = self.source.read()
+        finally:
+            if self.autoclose:
+                self.close()
+        return self.decode(data)
+
+    def close(self):
+        self.source.close()
+
+
+class FileOutput(Output):
+
+    """
+    Output for single, simple file-like objects.
+    """
+
+    def __init__(self, destination=None, destination_path=None,
+                 encoding=None, error_handler='strict', autoclose=1,
+                 handle_io_errors=1):
+        """
+        :Parameters:
+            - `destination`: either a file-like object (which is written
+              directly) or `None` (which implies `sys.stdout` if no
+              `destination_path` given).
+            - `destination_path`: a path to a file, which is opened and then
+              written.
+            - `autoclose`: close automatically after write (boolean); always
+              false if `sys.stdout` is the destination.
+        """
+        Output.__init__(self, destination, destination_path,
+                        encoding, error_handler)
+        self.opened = 1
+        self.autoclose = autoclose
+        self.handle_io_errors = handle_io_errors
+        if destination is None:
+            if destination_path:
+                self.opened = None
+            else:
+                self.destination = sys.stdout
+                self.autoclose = None
+        if not destination_path:
+            try:
+                self.destination_path = self.destination.name
+            except AttributeError:
+                pass
+
+    def open(self):
+        try:
+            self.destination = open(self.destination_path, 'w')
+        except IOError, error:
+            if not self.handle_io_errors:
+                raise
+            print >>sys.stderr, '%s: %s' % (error.__class__.__name__,
+                                            error)
+            print >>sys.stderr, ('Unable to open destination file for writing '
+                                 '(%r).  Exiting.' % self.destination_path)
+            sys.exit(1)
+        self.opened = 1
+
+    def write(self, data):
+        """Encode `data`, write it to a single file, and return it."""
+        output = self.encode(data)
+        if not self.opened:
+            self.open()
+        try:
+            self.destination.write(output)
+        finally:
+            if self.autoclose:
+                self.close()
+        return output
+
+    def close(self):
+        self.destination.close()
+        self.opened = None
+
+
+class StringInput(Input):
+
+    """
+    Direct string input.
+    """
+
+    default_source_path = '<string>'
+
+    def read(self):
+        """Decode and return the source string."""
+        return self.decode(self.source)
+
+
+class StringOutput(Output):
+
+    """
+    Direct string output.
+    """
+
+    default_destination_path = '<string>'
+
+    def write(self, data):
+        """Encode `data`, store it in `self.destination`, and return it."""
+        self.destination = self.encode(data)
+        return self.destination
+
+
+class NullInput(Input):
+
+    """
+    Degenerate input: read nothing.
+    """
+
+    default_source_path = 'null input'
+
+    def read(self):
+        """Return a null string."""
+        return u''
+
+
+class NullOutput(Output):
+
+    """
+    Degenerate output: write nothing.
+    """
+
+    default_destination_path = 'null output'
+
+    def write(self, data):
+        """Do nothing ([don't even] send data to the bit bucket)."""
+        pass

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,23 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# Internationalization details are documented in
+# <http://docutils.sf.net/docs/howto/i18n.html>.
+
+"""
+This package contains modules for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+_languages = {}
+
+def get_language(language_code):
+    if _languages.has_key(language_code):
+        return _languages[language_code]
+    module = __import__(language_code, globals(), locals())
+    _languages[language_code] = module
+    return module

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/af.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/af.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/af.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,60 @@
+# Author: Jannie Hofmeyr
+# Contact: jhsh at sun.ac.za
+# Revision: $Revision: 1.1.2.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Afrikaans-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      'author': 'Auteur',
+      'authors': 'Auteurs',
+      'organization': 'Organisasie',
+      'address': 'Adres',
+      'contact': 'Kontak',
+      'version': 'Weergawe',
+      'revision': 'Revisie',
+      'status': 'Status',
+      'date': 'Datum',
+      'copyright': 'Kopiereg',
+      'dedication': 'Opdrag',
+      'abstract': 'Opsomming',
+      'attention': 'Aandag!',
+      'caution': 'Wees versigtig!',
+      'danger': '!GEVAAR!',
+      'error': 'Fout',
+      'hint': 'Wenk',
+      'important': 'Belangrik',
+      'note': 'Nota',
+      'tip': 'Tip', # hint and tip both have the same translation: wenk
+      'warning': 'Waarskuwing',
+      'contents': 'Inhoud'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      'auteur': 'author',
+      'auteurs': 'authors',
+      'organisasie': 'organization',
+      'adres': 'address',
+      'kontak': 'contact',
+      'weergawe': 'version',
+      'revisie': 'revision',
+      'status': 'status',
+      'datum': 'date',
+      'kopiereg': 'copyright',
+      'opdrag': 'dedication',
+      'opsomming': 'abstract'}
+"""Afrikaans (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/cs.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/cs.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/cs.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,62 @@
+# Author: Marek Blaha
+# Contact: mb at dat.cz
+# Revision: $Revision: 1.1.4.4 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Czech-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      # fixed: language-dependent
+      'author': u'Autor',
+      'authors': u'Auto\u0159i',
+      'organization': u'Organizace',
+      'address': u'Adresa',
+      'contact': u'Kontakt',
+      'version': u'Verze',
+      'revision': u'Revize',
+      'status': u'Stav',
+      'date': u'Datum',
+      'copyright': u'Copyright',
+      'dedication': u'V\u011Bnov\u00E1n\u00ED',
+      'abstract': u'Abstrakt',
+      'attention': u'Pozor!',
+      'caution': u'Opatrn\u011B!',
+      'danger': u'!NEBEZPE\u010C\u00CD!',
+      'error': u'Chyba',
+      'hint': u'Rada',
+      'important': u'D\u016Fle\u017Eit\u00E9',
+      'note': u'Pozn\u00E1mka',
+      'tip': u'Tip',
+      'warning': u'Varov\u00E1n\u00ED',
+      'contents': u'Obsah'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      # language-dependent: fixed
+      u'autor': 'author',
+      u'auto\u0159i': 'authors',
+      u'organizace': 'organization',
+      u'adresa': 'address',
+      u'kontakt': 'contact',
+      u'verze': 'version',
+      u'revize': 'revision',
+      u'stav': 'status',
+      u'datum': 'date',
+      u'copyright': 'copyright',
+      u'v\u011Bnov\u00E1n\u00ED': 'dedication',
+      u'abstrakt': 'abstract'}
+"""Czech (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/de.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/de.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/de.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,60 @@
+# Authors:   David Goodger; Gunnar Schwant
+# Contact:   goodger at users.sourceforge.net
+# Revision:  $Revision: 1.2.10.7 $
+# Date:      $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+German language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+    'author': 'Autor',
+    'authors': 'Autoren',
+    'organization': 'Organisation',
+    'address': 'Adresse',
+    'contact': 'Kontakt',
+    'version': 'Version',
+    'revision': 'Revision',
+    'status': 'Status',
+    'date': 'Datum',
+    'dedication': 'Widmung',
+    'copyright': 'Copyright',
+    'abstract': 'Zusammenfassung',
+    'attention': 'Achtung!',
+    'caution': 'Vorsicht!',
+    'danger': '!GEFAHR!',
+    'error': 'Fehler',
+    'hint': 'Hinweis',
+    'important': 'Wichtig',
+    'note': 'Bemerkung',
+    'tip': 'Tipp',
+    'warning': 'Warnung',
+    'contents': 'Inhalt'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+    'autor': 'author',
+    'autoren': 'authors',
+    'organisation': 'organization',
+    'adresse': 'address',
+    'kontakt': 'contact',
+    'version': 'version',
+    'revision': 'revision',
+    'status': 'status',
+    'datum': 'date',
+    'copyright': 'copyright',
+    'widmung': 'dedication',
+    'zusammenfassung': 'abstract'}
+"""German (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/en.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/en.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/en.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,62 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+English-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      # fixed: language-dependent
+      'author': 'Author',
+      'authors': 'Authors',
+      'organization': 'Organization',
+      'address': 'Address',
+      'contact': 'Contact',
+      'version': 'Version',
+      'revision': 'Revision',
+      'status': 'Status',
+      'date': 'Date',
+      'copyright': 'Copyright',
+      'dedication': 'Dedication',
+      'abstract': 'Abstract',
+      'attention': 'Attention!',
+      'caution': 'Caution!',
+      'danger': '!DANGER!',
+      'error': 'Error',
+      'hint': 'Hint',
+      'important': 'Important',
+      'note': 'Note',
+      'tip': 'Tip',
+      'warning': 'Warning',
+      'contents': 'Contents'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      # language-dependent: fixed
+      'author': 'author',
+      'authors': 'authors',
+      'organization': 'organization',
+      'address': 'address',
+      'contact': 'contact',
+      'version': 'version',
+      'revision': 'revision',
+      'status': 'status',
+      'date': 'date',
+      'copyright': 'copyright',
+      'dedication': 'dedication',
+      'abstract': 'abstract'}
+"""English (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/eo.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/eo.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/eo.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,63 @@
+# Author: Marcelo Huerta San Martin
+# Contact: richieadler at users.sourceforge.net
+# Revision: $Revision: 1.1.2.5 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Esperanto-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      # fixed: language-dependent
+      'author': u'A\u016dtoro',
+      'authors': u'A\u016dtoroj',
+      'organization': u'Organizo',
+      'address': u'Adreso',
+      'contact': u'Kontakto',
+      'version': u'Versio',
+      'revision': u'Revido',
+      'status': u'Stato',
+      'date': u'Dato',
+      # 'copyright': u'Kopirajto',
+      'copyright': u'A\u016dtorrajto',
+      'dedication': u'Dedi\u0109o',
+      'abstract': u'Resumo',
+      'attention': u'Atentu!',
+      'caution': u'Zorgu!',
+      'danger': u'DAN\u011cERO!',
+      'error': u'Eraro',
+      'hint': u'Spuro',
+      'important': u'Grava',
+      'note': u'Noto',
+      'tip': u'Helpeto',
+      'warning': u'Averto',
+      'contents': u'Enhavo'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      # language-dependent: fixed
+      'a\u016dtoro': 'author',
+      'a\u016dtoroj': 'authors',
+      'organizo': 'organization',
+      'adreso': 'address',
+      'kontakto': 'contact',
+      'versio': 'version',
+      'revido': 'revision',
+      'stato': 'status',
+      'dato': 'date',
+      'a\u016dtorrajto': 'copyright',
+      'dedi\u0109o': 'dedication',
+      'resumo': 'abstract'}
+"""Esperanto (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/es.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/es.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/es.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,61 @@
+# -*- coding: iso-8859-1 -*-
+# Author: Marcelo Huerta San Martín
+# Contact: mghsm at uol.com.ar
+# Revision: $Revision: 1.1.2.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Spanish-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      'author': u'Autor',
+      'authors': u'Autores',
+      'organization': u'Organizaci\u00f3n',
+      'address': u'Direcci\u00f3n',
+      'contact': u'Contacto',
+      'version': u'Versi\u00f3n',
+      'revision': u'Revisi\u00f3n',
+      'status': u'Estado',
+      'date': u'Fecha',
+      'copyright': u'Copyright',
+      'dedication': u'Dedicatoria',
+      'abstract': u'Resumen',
+      'attention': u'\u00a1Atenci\u00f3n!',
+      'caution': u'\u00a1Precauci\u00f3n!',
+      'danger': u'\u00a1PELIGRO!',
+      'error': u'Error',
+      'hint': u'Sugerencia',
+      'important': u'Importante',
+      'note': u'Nota',
+      'tip': u'Consejo',
+      'warning': u'Advertencia',
+      'contents': u'Contenido'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      u'autor': 'author',
+      u'autores': 'authors',
+      u'organizaci\u00f3n': 'organization',
+      u'direcci\u00f3n': 'address',
+      u'contacto': 'contact',
+      u'versi\u00f3n': 'version',
+      u'revisi\u00f3n': 'revision',
+      u'estado': 'status',
+      u'fecha': 'date',
+      u'copyright': 'copyright',
+      u'dedicatoria': 'dedication',
+      u'resumen': 'abstract'}
+"""Spanish (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/fi.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/fi.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/fi.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,62 @@
+# Author: Asko Soukka
+# Contact: asko.soukka at iki.fi
+# Revision: $Revision: 1.1.2.1 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Finnish-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      # fixed: language-dependent
+      u'author': u'Tekij\u00e4',
+      u'authors': u'Tekij\u00e4t',
+      u'organization': u'Yhteis\u00f6',
+      u'address': u'Osoite',
+      u'contact': u'Yhteystiedot',
+      u'version': u'Versio',
+      u'revision': u'Vedos',
+      u'status': u'Tila',
+      u'date': u'P\u00e4iv\u00e4ys',
+      u'copyright': u'Tekij\u00e4noikeudet',
+      u'dedication': u'Omistuskirjoitus',
+      u'abstract': u'Tiivistelm\u00e4',
+      u'attention': u'Huomio!',
+      u'caution': u'Varo!',
+      u'danger': u'!VAARA!',
+      u'error': u'Virhe',
+      u'hint': u'Vihje',
+      u'important': u'T\u00e4rke\u00e4\u00e4',
+      u'note': u'Huomautus',
+      u'tip': u'Neuvo',
+      u'warning': u'Varoitus',
+      u'contents': u'Sis\u00e4llys'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      # language-dependent: fixed
+      u'tekij\u00e4': u'author',
+      u'tekij\u00e4t': u'authors',
+      u'yhteis\u00f6': u'organization',
+      u'osoite': u'address',
+      u'yhteystiedot': u'contact',
+      u'versio': u'version',
+      u'vedos': u'revision',
+      u'tila': u'status',
+      u'p\u00e4iv\u00e4ys': u'date',
+      u'tekij\u00e4noikeudet': u'copyright',
+      u'omistuskirjoitus': u'dedication',
+      u'tiivistelm\u00e4': u'abstract'}
+"""Finnish (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/fr.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/fr.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/fr.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,60 @@
+# Author: Stefane Fermigier
+# Contact: sf at fermigier.com
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+French-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      u'author': u'Auteur',
+      u'authors': u'Auteurs',
+      u'organization': u'Organisation',
+      u'address': u'Adresse',
+      u'contact': u'Contact',
+      u'version': u'Version',
+      u'revision': u'R\u00e9vision',
+      u'status': u'Statut',
+      u'date': u'Date',
+      u'copyright': u'Copyright',
+      u'dedication': u'D\u00e9dicace',
+      u'abstract': u'R\u00e9sum\u00e9',
+      u'attention': u'Attention!',
+      u'caution': u'Avertissement!',
+      u'danger': u'!DANGER!',
+      u'error': u'Erreur',
+      u'hint': u'Indication',
+      u'important': u'Important',
+      u'note': u'Note',
+      u'tip': u'Astuce',
+      u'warning': u'Avis',
+      u'contents': u'Sommaire'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      u'auteur': u'author',
+      u'auteurs': u'authors',
+      u'organisation': u'organization',
+      u'adresse': u'address',
+      u'contact': u'contact',
+      u'version': u'version',
+      u'r\u00e9vision': u'revision',
+      u'statut': u'status',
+      u'date': u'date',
+      u'copyright': u'copyright',
+      u'd\u00e9dicace': u'dedication',
+      u'r\u00e9sum\u00e9': u'abstract'}
+"""French (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/it.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/it.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/it.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,60 @@
+# Author: Nicola Larosa
+# Contact: docutils at tekNico.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Italian-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      'author': 'Autore',
+      'authors': 'Autori',
+      'organization': 'Organizzazione',
+      'address': 'Indirizzo',
+      'contact': 'Contatti',
+      'version': 'Versione',
+      'revision': 'Revisione',
+      'status': 'Status',
+      'date': 'Data',
+      'copyright': 'Copyright',
+      'dedication': 'Dedica',
+      'abstract': 'Riassunto',
+      'attention': 'Attenzione!',
+      'caution': 'Cautela!',
+      'danger': '!PERICOLO!',
+      'error': 'Errore',
+      'hint': 'Suggerimento',
+      'important': 'Importante',
+      'note': 'Nota',
+      'tip': 'Consiglio',
+      'warning': 'Avvertenza',
+      'contents': 'Indice'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      'autore': 'author',
+      'autori': 'authors',
+      'organizzazione': 'organization',
+      'indirizzo': 'address',
+      'contatti': 'contact',
+      'versione': 'version',
+      'revisione': 'revision',
+      'status': 'status',
+      'data': 'date',
+      'copyright': 'copyright',
+      'dedica': 'dedication',
+      'riassunto': 'abstract'}
+"""Italian (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/pt_br.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/pt_br.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/pt_br.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,62 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.1.4.4 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Brazilian Portuguese-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      # fixed: language-dependent
+      'author': u'Autor',
+      'authors': u'Autores',
+      'organization': u'Organiza\u00E7\u00E3o',
+      'address': u'Endere\u00E7o',
+      'contact': u'Contato',
+      'version': u'Vers\u00E3o',
+      'revision': u'Revis\u00E3o',
+      'status': u'Estado',
+      'date': u'Data',
+      'copyright': u'Copyright',
+      'dedication': u'Dedicat\u00F3ria',
+      'abstract': u'Resumo',
+      'attention': u'Atten\u00E7\u00E3o!',
+      'caution': u'Cuidado!',
+      'danger': u'PERIGO!',
+      'error': u'Erro',
+      'hint': u'Sugest\u00E3o',
+      'important': u'Importante',
+      'note': u'Nota',
+      'tip': u'Dica',
+      'warning': u'Aviso',
+      'contents': u'Sum\u00E1rio'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      # language-dependent: fixed
+      u'autor': 'author',
+      u'autores': 'authors',
+      u'organiza\u00E7\u00E3o': 'organization',
+      u'endere\u00E7o': 'address',
+      u'contato': 'contact',
+      u'vers\u00E3o': 'version',
+      u'revis\u00E3o': 'revision',
+      u'estado': 'status',
+      u'data': 'date',
+      u'copyright': 'copyright',
+      u'dedicat\u00F3ria': 'dedication',
+      u'resumo': 'abstract'}
+"""Brazilian Portuguese (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/ru.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/ru.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/ru.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,68 @@
+# Author: Roman Suzi
+# Contact: rnd at onego.ru
+# Revision: $Revision: 1.1.2.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Russian-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      u'abstract': u'\u0410\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044f',
+      u'address': u'\u0410\u0434\u0440\u0435\u0441',
+      u'attention': u'\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435!',
+      u'author': u'\u0410\u0432\u0442\u043e\u0440',
+      u'authors': u'\u0410\u0432\u0442\u043e\u0440\u044b',
+      u'caution': u'\u041e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e!',
+      u'contact': u'\u041a\u043e\u043d\u0442\u0430\u043a\u0442',
+      u'contents':
+      u'\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435',
+      u'copyright': u'\u041f\u0440\u0430\u0432\u0430 '
+      u'\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f',
+      u'danger': u'\u041e\u041f\u0410\u0421\u041d\u041e!',
+      u'date': u'\u0414\u0430\u0442\u0430',
+      u'dedication':
+      u'\u041f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0438\u0435',
+      u'error': u'\u041e\u0448\u0438\u0431\u043a\u0430',
+      u'hint': u'\u0421\u043e\u0432\u0435\u0442',
+      u'important': u'\u0412\u0430\u0436\u043d\u043e',
+      u'note': u'\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435',
+      u'organization':
+      u'\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f',
+      u'revision': u'\u0420\u0435\u0434\u0430\u043a\u0446\u0438\u044f',
+      u'status': u'\u0421\u0442\u0430\u0442\u0443\u0441',
+      u'tip': u'\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430',
+      u'version': u'\u0412\u0435\u0440\u0441\u0438\u044f',
+      u'warning': u'\u041f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436'
+      u'\u0434\u0435\u043d\u0438\u0435'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      u'\u0410\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044f': u'abstract',
+      u'\u0410\u0434\u0440\u0435\u0441': u'address',
+      u'\u0410\u0432\u0442\u043e\u0440': u'author',
+      u'\u0410\u0432\u0442\u043e\u0440\u044b': u'authors',
+      u'\u041a\u043e\u043d\u0442\u0430\u043a\u0442': u'contact',
+      u'\u041f\u0440\u0430\u0432\u0430 \u043a\u043e\u043f\u0438\u0440\u043e'
+      u'\u0432\u0430\u043d\u0438\u044f': u'copyright',
+      u'\u0414\u0430\u0442\u0430': u'date',
+      u'\u041f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0438\u0435':
+      u'dedication',
+      u'\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f':
+      u'organization',
+      u'\u0420\u0435\u0434\u0430\u043a\u0446\u0438\u044f': u'revision',
+      u'\u0421\u0442\u0430\u0442\u0443\u0441': u'status',
+      u'\u0412\u0435\u0440\u0441\u0438\u044f': u'version'}
+"""Russian (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators =  [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/sk.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/sk.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/sk.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,60 @@
+# :Author: Miroslav Vasko
+# :Contact: zemiak at zoznam.sk
+# :Revision: $Revision: 1.2.10.7 $
+# :Date: $Date: 2005/01/07 13:26:02 $
+# :Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Slovak-language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      'author': u'Autor',
+      'authors': u'Autori',
+      'organization': u'Organiz\u00E1cia',
+      'address': u'Adresa',
+      'contact': u'Kontakt',
+      'version': u'Verzia',
+      'revision': u'Rev\u00EDzia',
+      'status': u'Stav',
+      'date': u'D\u00E1tum',
+      'copyright': u'Copyright',
+      'dedication': u'Venovanie',
+      'abstract': u'Abstraktne',
+      'attention': u'Pozor!',
+      'caution': u'Opatrne!',
+      'danger': u'!NEBEZPE\u010cENSTVO!',
+      'error': u'Chyba',
+      'hint': u'Rada',
+      'important': u'D\u00F4le\u017Eit\u00E9',
+      'note': u'Pozn\u00E1mka',
+      'tip': u'Tip',
+      'warning': u'Varovanie',
+      'contents': u'Obsah'}
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      u'autor': 'author',
+      u'autori': 'authors',
+      u'organiz\u00E1cia': 'organization',
+      u'adresa': 'address',
+      u'kontakt': 'contact',
+      u'verzia': 'version',
+      u'rev\u00EDzia': 'revision',
+      u'stav': 'status',
+      u'd\u00E1tum': 'date',
+      u'copyright': 'copyright',
+      u'venovanie': 'dedication',
+      u'abstraktne': 'abstract'}
+"""Slovak (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/sv.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/sv.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/sv.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,61 @@
+# Author:    Adam Chodorowski
+# Contact:   chodorowski at users.sourceforge.net
+# Revision:  $Revision: 1.2.10.7 $
+# Date:      $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Swedish language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+    'author':       u'F\u00f6rfattare',
+    'authors':      u'F\u00f6rfattare',
+    'organization': u'Organisation',
+    'address':      u'Adress',
+    'contact':      u'Kontakt',
+    'version':      u'Version',
+    'revision':     u'Revision',
+    'status':       u'Status',
+    'date':         u'Datum',
+    'copyright':    u'Copyright',
+    'dedication':   u'Dedikation',
+    'abstract':     u'Sammanfattning',
+    'attention':    u'Observera!',
+    'caution':      u'Varning!',
+    'danger':       u'FARA!',
+    'error':        u'Fel',
+    'hint':         u'V\u00e4gledning',
+    'important':    u'Viktigt',
+    'note':         u'Notera',
+    'tip':          u'Tips',
+    'warning':      u'Varning',
+    'contents':     u'Inneh\u00e5ll' }
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+    # 'Author' and 'Authors' identical in Swedish; assume the plural:
+    u'f\u00f6rfattare': 'authors',
+    u' n/a':            'author',
+    u'organisation':    'organization',
+    u'adress':          'address',
+    u'kontakt':         'contact',
+    u'version':         'version',
+    u'revision':        'revision',
+    u'status':          'status',
+    u'datum':           'date',
+    u'copyright':       'copyright',
+    u'dedikation':      'dedication', 
+    u'sammanfattning':  'abstract' }
+"""Swedish (lowcased) to canonical name mapping for bibliographic fields."""
+
+author_separators = [';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/zh_tw.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/zh_tw.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/languages/zh_tw.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,64 @@
+# Author: Joe YS Jaw
+# Contact: joeysj at users.sourceforge.net
+# Revision: $Revision: 1.1.2.1 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Traditional Chinese language mappings for language-dependent features of Docutils.
+"""
+
+__docformat__ = 'reStructuredText'
+
+labels = {
+      # fixed: language-dependent
+      'author': u'\u4f5c\u8005', # 'Author',
+      'authors': u'\u4f5c\u8005\u7fa4', # 'Authors',
+      'organization': u'\u7d44\u7e54', # 'Organization',
+      'address': u'\u5730\u5740', # 'Address',
+      'contact': u'\u9023\u7d61', # 'Contact',
+      'version': u'\u7248\u672c', # 'Version',
+      'revision': u'\u4fee\u8a02', # 'Revision',
+      'status': u'\u72c0\u614b', # 'Status',
+      'date': u'\u65e5\u671f', # 'Date',
+      'copyright': u'\u7248\u6b0a', # 'Copyright',
+      'dedication': u'\u984c\u737b', # 'Dedication',
+      'abstract': u'\u6458\u8981', # 'Abstract',
+      'attention': u'\u6ce8\u610f\uff01', # 'Attention!',
+      'caution': u'\u5c0f\u5fc3\uff01', # 'Caution!',
+      'danger': u'\uff01\u5371\u96aa\uff01', # '!DANGER!',
+      'error': u'\u932f\u8aa4', # 'Error',
+      'hint': u'\u63d0\u793a', # 'Hint',
+      'important': u'\u91cd\u8981', # 'Important',
+      'note': u'\u8a3b\u89e3', # 'Note',
+      'tip': u'\u79d8\u8a23', # 'Tip',
+      'warning': u'\u8b66\u544a', # 'Warning',
+      'contents': u'\u76ee\u9304' # 'Contents'
+} 
+"""Mapping of node class name to label text."""
+
+bibliographic_fields = {
+      # language-dependent: fixed
+      'author': 'author',
+      'authors': 'authors',
+      'organization': 'organization',
+      'address': 'address',
+      'contact': 'contact',
+      'version': 'version',
+      'revision': 'revision',
+      'status': 'status',
+      'date': 'date',
+      'copyright': 'copyright',
+      'dedication': 'dedication',
+      'abstract': 'abstract'}
+"""Traditional Chinese to canonical name mapping for bibliographic fields."""
+
+author_separators = [u'\uff1b', u'\uff0c', u'\u3001',
+                     ';', ',']
+"""List of separator strings for the 'Authors' bibliographic field. Tried in
+order."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/nodes.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/nodes.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/nodes.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,1547 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Docutils document tree element class library.
+
+Classes in CamelCase are abstract base classes or auxiliary classes. The one
+exception is `Text`, for a text (PCDATA) node; uppercase is used to
+differentiate from element classes.  Classes in lower_case_with_underscores
+are element classes, matching the XML element generic identifiers in the DTD_.
+
+The position of each node (the level at which it can occur) is significant and
+is represented by abstract base classes (`Root`, `Structural`, `Body`,
+`Inline`, etc.).  Certain transformations will be easier because we can use
+``isinstance(node, base_class)`` to determine the position of the node in the
+hierarchy.
+
+.. _DTD: http://docutils.sourceforge.net/docs/ref/docutils.dtd
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import os
+import re
+import xml.dom.minidom
+from types import IntType, SliceType, StringType, UnicodeType, \
+     TupleType, ListType
+from UserString import UserString
+
+
+# ==============================
+#  Functional Node Base Classes
+# ==============================
+
+class Node:
+
+    """Abstract base class of nodes in a document tree."""
+
+    parent = None
+    """Back-reference to the Node immediately containing this Node."""
+
+    document = None
+    """The `document` node at the root of the tree containing this Node."""
+
+    source = None
+    """Path or description of the input source which generated this Node."""
+
+    line = None
+    """The line number (1-based) of the beginning of this Node in `source`."""
+
+    def __nonzero__(self):
+        """
+        Node instances are always true, even if they're empty.  A node is more
+        than a simple container.  Its boolean "truth" does not depend on
+        having one or more subnodes in the doctree.
+
+        Use `len()` to check node length.  Use `None` to represent a boolean
+        false value.
+        """
+        return 1
+
+    def asdom(self, dom=xml.dom.minidom):
+        """Return a DOM **fragment** representation of this Node."""
+        domroot = dom.Document()
+        return self._dom_node(domroot)
+
+    def pformat(self, indent='    ', level=0):
+        """
+        Return an indented pseudo-XML representation, for test purposes.
+
+        Override in subclasses.
+        """
+        raise NotImplementedError
+
+    def copy(self):
+        """Return a copy of self."""
+        raise NotImplementedError
+
+    def setup_child(self, child):
+        child.parent = self
+        if self.document:
+            child.document = self.document
+            if child.source is None:
+                child.source = self.document.current_source
+            if child.line is None:
+                child.line = self.document.current_line
+
+    def walk(self, visitor):
+        """
+        Traverse a tree of `Node` objects, calling the
+        `dispatch_visit()` method of `visitor` when entering each
+        node.  (The `walkabout()` method is similar, except it also
+        calls the `dispatch_departure()` method before exiting each
+        node.)
+
+        This tree traversal supports limited in-place tree
+        modifications.  Replacing one node with one or more nodes is
+        OK, as is removing an element.  However, if the node removed
+        or replaced occurs after the current node, the old node will
+        still be traversed, and any new nodes will not.
+
+        Within ``visit`` methods (and ``depart`` methods for 
+        `walkabout()`), `TreePruningException` subclasses may be raised
+        (`SkipChildren`, `SkipSiblings`, `SkipNode`, `SkipDeparture`).
+
+        Parameter `visitor`: A `NodeVisitor` object, containing a
+        ``visit`` implementation for each `Node` subclass encountered.
+        """
+        visitor.document.reporter.debug(
+            'calling dispatch_visit for %s' % self.__class__.__name__,
+            category='nodes.Node.walk')
+        try:
+            visitor.dispatch_visit(self)
+        except (SkipChildren, SkipNode):
+            return
+        except SkipDeparture:           # not applicable; ignore
+            pass
+        children = self.get_children()
+        try:
+            for child in children[:]:
+                child.walk(visitor)
+        except SkipSiblings:
+            pass
+
+    def walkabout(self, visitor):
+        """
+        Perform a tree traversal similarly to `Node.walk()` (which
+        see), except also call the `dispatch_departure()` method
+        before exiting each node.
+
+        Parameter `visitor`: A `NodeVisitor` object, containing a
+        ``visit`` and ``depart`` implementation for each `Node`
+        subclass encountered.
+        """
+        call_depart = 1
+        visitor.document.reporter.debug(
+            'calling dispatch_visit for %s' % self.__class__.__name__,
+            category='nodes.Node.walkabout')
+        try:
+            try:
+                visitor.dispatch_visit(self)
+            except SkipNode:
+                return
+            except SkipDeparture:
+                call_depart = 0
+            children = self.get_children()
+            try:
+                for child in children[:]:
+                    child.walkabout(visitor)
+            except SkipSiblings:
+                pass
+        except SkipChildren:
+            pass
+        if call_depart:
+            visitor.document.reporter.debug(
+                'calling dispatch_departure for %s' % self.__class__.__name__,
+                category='nodes.Node.walkabout')
+            visitor.dispatch_departure(self)
+
+
+class Text(Node, UserString):
+
+    """
+    Instances are terminal nodes (leaves) containing text only; no child
+    nodes or attributes.  Initialize by passing a string to the constructor.
+    Access the text itself with the `astext` method.
+    """
+
+    tagname = '#text'
+
+    def __init__(self, data, rawsource=''):
+        UserString.__init__(self, data)
+
+        self.rawsource = rawsource
+        """The raw text from which this element was constructed."""
+
+    def __repr__(self):
+        data = repr(self.data)
+        if len(data) > 70:
+            data = repr(self.data[:64] + ' ...')
+        return '<%s: %s>' % (self.tagname, data)
+
+    def __len__(self):
+        return len(self.data)
+
+    def shortrepr(self):
+        data = repr(self.data)
+        if len(data) > 20:
+            data = repr(self.data[:16] + ' ...')
+        return '<%s: %s>' % (self.tagname, data)
+
+    def _dom_node(self, domroot):
+        return domroot.createTextNode(self.data)
+
+    def astext(self):
+        return self.data
+
+    def copy(self):
+        return self.__class__(self.data)
+
+    def pformat(self, indent='    ', level=0):
+        result = []
+        indent = indent * level
+        for line in self.data.splitlines():
+            result.append(indent + line + '\n')
+        return ''.join(result)
+
+    def get_children(self):
+        """Text nodes have no children. Return []."""
+        return []
+
+
+class Element(Node):
+
+    """
+    `Element` is the superclass to all specific elements.
+
+    Elements contain attributes and child nodes.  Elements emulate
+    dictionaries for attributes, indexing by attribute name (a string).  To
+    set the attribute 'att' to 'value', do::
+
+        element['att'] = 'value'
+
+    Elements also emulate lists for child nodes (element nodes and/or text
+    nodes), indexing by integer.  To get the first child node, use::
+
+        element[0]
+
+    Elements may be constructed using the ``+=`` operator.  To add one new
+    child node to element, do::
+
+        element += node
+
+    This is equivalent to ``element.append(node)``.
+
+    To add a list of multiple child nodes at once, use the same ``+=``
+    operator::
+
+        element += [node1, node2]
+
+    This is equivalent to ``element.extend([node1, node2])``.
+    """
+
+    tagname = None
+    """The element generic identifier. If None, it is set as an instance
+    attribute to the name of the class."""
+
+    child_text_separator = '\n\n'
+    """Separator for child nodes, used by `astext()` method."""
+
+    def __init__(self, rawsource='', *children, **attributes):
+        self.rawsource = rawsource
+        """The raw text from which this element was constructed."""
+
+        self.children = []
+        """List of child nodes (elements and/or `Text`)."""
+
+        self.extend(children)           # maintain parent info
+
+        self.attributes = {}
+        """Dictionary of attribute {name: value}."""
+
+        for att, value in attributes.items():
+            self.attributes[att.lower()] = value
+
+        if self.tagname is None:
+            self.tagname = self.__class__.__name__
+
+    def _dom_node(self, domroot):
+        element = domroot.createElement(self.tagname)
+        for attribute, value in self.attributes.items():
+            if isinstance(value, ListType):
+                value = ' '.join(['%s' % v for v in value])
+            element.setAttribute(attribute, '%s' % value)
+        for child in self.children:
+            element.appendChild(child._dom_node(domroot))
+        return element
+
+    def __repr__(self):
+        data = ''
+        for c in self.children:
+            data += c.shortrepr()
+            if len(data) > 60:
+                data = data[:56] + ' ...'
+                break
+        if self.hasattr('name'):
+            return '<%s "%s": %s>' % (self.__class__.__name__,
+                                      self.attributes['name'], data)
+        else:
+            return '<%s: %s>' % (self.__class__.__name__, data)
+
+    def shortrepr(self):
+        if self.hasattr('name'):
+            return '<%s "%s"...>' % (self.__class__.__name__,
+                                      self.attributes['name'])
+        else:
+            return '<%s...>' % self.tagname
+
+    def __str__(self):
+        return self.__unicode__().encode('raw_unicode_escape')
+
+    def __unicode__(self):
+        if self.children:
+            return u'%s%s%s' % (self.starttag(),
+                                 ''.join([str(c) for c in self.children]),
+                                 self.endtag())
+        else:
+            return self.emptytag()
+
+    def starttag(self):
+        parts = [self.tagname]
+        for name, value in self.attlist():
+            if value is None:           # boolean attribute
+                parts.append(name)
+            elif isinstance(value, ListType):
+                values = ['%s' % v for v in value]
+                parts.append('%s="%s"' % (name, ' '.join(values)))
+            else:
+                parts.append('%s="%s"' % (name, value))
+        return '<%s>' % ' '.join(parts)
+
+    def endtag(self):
+        return '</%s>' % self.tagname
+
+    def emptytag(self):
+        return u'<%s/>' % ' '.join([self.tagname] +
+                                    ['%s="%s"' % (n, v)
+                                     for n, v in self.attlist()])
+
+    def __len__(self):
+        return len(self.children)
+
+    def __getitem__(self, key):
+        if isinstance(key, UnicodeType) or isinstance(key, StringType):
+            return self.attributes[key]
+        elif isinstance(key, IntType):
+            return self.children[key]
+        elif isinstance(key, SliceType):
+            assert key.step in (None, 1), 'cannot handle slice with stride'
+            return self.children[key.start:key.stop]
+        else:
+            raise TypeError, ('element index must be an integer, a slice, or '
+                              'an attribute name string')
+
+    def __setitem__(self, key, item):
+        if isinstance(key, UnicodeType) or isinstance(key, StringType):
+            self.attributes[str(key)] = item
+        elif isinstance(key, IntType):
+            self.setup_child(item)
+            self.children[key] = item
+        elif isinstance(key, SliceType):
+            assert key.step in (None, 1), 'cannot handle slice with stride'
+            for node in item:
+                self.setup_child(node)
+            self.children[key.start:key.stop] = item
+        else:
+            raise TypeError, ('element index must be an integer, a slice, or '
+                              'an attribute name string')
+
+    def __delitem__(self, key):
+        if isinstance(key, UnicodeType) or isinstance(key, StringType):
+            del self.attributes[key]
+        elif isinstance(key, IntType):
+            del self.children[key]
+        elif isinstance(key, SliceType):
+            assert key.step in (None, 1), 'cannot handle slice with stride'
+            del self.children[key.start:key.stop]
+        else:
+            raise TypeError, ('element index must be an integer, a simple '
+                              'slice, or an attribute name string')
+
+    def __add__(self, other):
+        return self.children + other
+
+    def __radd__(self, other):
+        return other + self.children
+
+    def __iadd__(self, other):
+        """Append a node or a list of nodes to `self.children`."""
+        if isinstance(other, Node):
+            self.setup_child(other)
+            self.children.append(other)
+        elif other is not None:
+            for node in other:
+                self.setup_child(node)
+            self.children.extend(other)
+        return self
+
+    def astext(self):
+        return self.child_text_separator.join(
+              [child.astext() for child in self.children])
+
+    def attlist(self):
+        attlist = self.attributes.items()
+        attlist.sort()
+        return attlist
+
+    def get(self, key, failobj=None):
+        return self.attributes.get(key, failobj)
+
+    def hasattr(self, attr):
+        return self.attributes.has_key(attr)
+
+    def delattr(self, attr):
+        if self.attributes.has_key(attr):
+            del self.attributes[attr]
+
+    def setdefault(self, key, failobj=None):
+        return self.attributes.setdefault(key, failobj)
+
+    has_key = hasattr
+
+    def append(self, item):
+        self.setup_child(item)
+        self.children.append(item)
+
+    def extend(self, item):
+        for node in item:
+            self.setup_child(node)
+        self.children.extend(item)
+
+    def insert(self, index, item):
+        if isinstance(item, Node):
+            self.setup_child(item)
+            self.children.insert(index, item)
+        elif item is not None:
+            self[index:index] = item
+
+    def pop(self, i=-1):
+        return self.children.pop(i)
+
+    def remove(self, item):
+        self.children.remove(item)
+
+    def index(self, item):
+        return self.children.index(item)
+
+    def replace(self, old, new):
+        """Replace one child `Node` with another child or children."""
+        index = self.index(old)
+        if isinstance(new, Node):
+            self.setup_child(new)
+            self[index] = new
+        elif new is not None:
+            self[index:index+1] = new
+
+    def first_child_matching_class(self, childclass, start=0, end=sys.maxint):
+        """
+        Return the index of the first child whose class exactly matches.
+
+        Parameters:
+
+        - `childclass`: A `Node` subclass to search for, or a tuple of `Node`
+          classes. If a tuple, any of the classes may match.
+        - `start`: Initial index to check.
+        - `end`: Initial index to *not* check.
+        """
+        if not isinstance(childclass, TupleType):
+            childclass = (childclass,)
+        for index in range(start, min(len(self), end)):
+            for c in childclass:
+                if isinstance(self[index], c):
+                    return index
+        return None
+
+    def first_child_not_matching_class(self, childclass, start=0,
+                                       end=sys.maxint):
+        """
+        Return the index of the first child whose class does *not* match.
+
+        Parameters:
+
+        - `childclass`: A `Node` subclass to skip, or a tuple of `Node`
+          classes. If a tuple, none of the classes may match.
+        - `start`: Initial index to check.
+        - `end`: Initial index to *not* check.
+        """
+        if not isinstance(childclass, TupleType):
+            childclass = (childclass,)
+        for index in range(start, min(len(self), end)):
+            match = 0
+            for c in childclass:
+                if isinstance(self.children[index], c):
+                    match = 1
+                    break
+            if not match:
+                return index
+        return None
+
+    def pformat(self, indent='    ', level=0):
+        return ''.join(['%s%s\n' % (indent * level, self.starttag())] +
+                       [child.pformat(indent, level+1)
+                        for child in self.children])
+
+    def get_children(self):
+        """Return this element's children."""
+        return self.children
+
+    def copy(self):
+        return self.__class__(**self.attributes)
+
+    def set_class(self, name):
+        """Add a new name to the "class" attribute."""
+        self.attributes['class'] = (self.attributes.get('class', '') + ' '
+                                    + name.lower()).strip()
+
+
+class TextElement(Element):
+
+    """
+    An element which directly contains text.
+
+    Its children are all `Text` or `TextElement` subclass nodes.  You can
+    check whether an element's context is inline simply by checking whether
+    its immediate parent is a `TextElement` instance (including subclasses).
+    This is handy for nodes like `image` that can appear both inline and as
+    standalone body elements.
+
+    If passing children to `__init__()`, make sure to set `text` to
+    ``''`` or some other suitable value.
+    """
+
+    child_text_separator = ''
+    """Separator for child nodes, used by `astext()` method."""
+
+    def __init__(self, rawsource='', text='', *children, **attributes):
+        if text != '':
+            textnode = Text(text)
+            Element.__init__(self, rawsource, textnode, *children,
+                              **attributes)
+        else:
+            Element.__init__(self, rawsource, *children, **attributes)
+
+
+class FixedTextElement(TextElement):
+
+    """An element which directly contains preformatted text."""
+
+    def __init__(self, rawsource='', text='', *children, **attributes):
+        TextElement.__init__(self, rawsource, text, *children, **attributes)
+        self.attributes['xml:space'] = 'preserve'
+
+
+# ========
+#  Mixins
+# ========
+
+class Resolvable:
+
+    resolved = 0
+
+
+class BackLinkable:
+
+    def add_backref(self, refid):
+        self.setdefault('backrefs', []).append(refid)
+
+
+# ====================
+#  Element Categories
+# ====================
+
+class Root: pass
+
+class Titular: pass
+
+class PreDecorative:
+    """Category of Node which may occur before Decorative Nodes."""
+
+class PreBibliographic(PreDecorative):
+    """Category of Node which may occur before Bibliographic Nodes."""
+
+class Bibliographic(PreDecorative): pass
+
+class Decorative: pass
+
+class Structural: pass
+
+class Body: pass
+
+class General(Body): pass
+
+class Sequential(Body): pass
+
+class Admonition(Body): pass
+
+class Special(Body):
+    """Special internal body elements."""
+
+class Invisible(PreBibliographic):
+    """Internal elements that don't appear in output."""
+
+class Part: pass
+
+class Inline: pass
+
+class Referential(Resolvable): pass
+
+class Targetable(Resolvable):
+
+    referenced = 0
+
+    indirect_reference_name = None
+    """Holds the whitespace_normalized_name (contains mixed case) of a target"""
+
+class Labeled:
+    """Contains a `label` as its first element."""
+
+
+# ==============
+#  Root Element
+# ==============
+
+class document(Root, Structural, Element):
+
+    def __init__(self, settings, reporter, *args, **kwargs):
+        Element.__init__(self, *args, **kwargs)
+
+        self.current_source = None
+        """Path to or description of the input source being processed."""
+
+        self.current_line = None
+        """Line number (1-based) of `current_source`."""
+
+        self.settings = settings
+        """Runtime settings data record."""
+
+        self.reporter = reporter
+        """System message generator."""
+
+        self.external_targets = []
+        """List of external target nodes."""
+
+        self.internal_targets = []
+        """List of internal target nodes."""
+
+        self.indirect_targets = []
+        """List of indirect target nodes."""
+
+        self.substitution_defs = {}
+        """Mapping of substitution names to substitution_definition nodes."""
+
+        self.substitution_names = {}
+        """Mapping of case-normalized substitution names to case-sensitive
+        names."""
+
+        self.refnames = {}
+        """Mapping of names to lists of referencing nodes."""
+
+        self.refids = {}
+        """Mapping of ids to lists of referencing nodes."""
+
+        self.nameids = {}
+        """Mapping of names to unique id's."""
+
+        self.nametypes = {}
+        """Mapping of names to hyperlink type (boolean: True => explicit,
+        False => implicit."""
+
+        self.ids = {}
+        """Mapping of ids to nodes."""
+
+        self.substitution_refs = {}
+        """Mapping of substitution names to lists of substitution_reference
+        nodes."""
+
+        self.footnote_refs = {}
+        """Mapping of footnote labels to lists of footnote_reference nodes."""
+
+        self.citation_refs = {}
+        """Mapping of citation labels to lists of citation_reference nodes."""
+
+        self.anonymous_targets = []
+        """List of anonymous target nodes."""
+
+        self.anonymous_refs = []
+        """List of anonymous reference nodes."""
+
+        self.autofootnotes = []
+        """List of auto-numbered footnote nodes."""
+
+        self.autofootnote_refs = []
+        """List of auto-numbered footnote_reference nodes."""
+
+        self.symbol_footnotes = []
+        """List of symbol footnote nodes."""
+
+        self.symbol_footnote_refs = []
+        """List of symbol footnote_reference nodes."""
+
+        self.footnotes = []
+        """List of manually-numbered footnote nodes."""
+
+        self.citations = []
+        """List of citation nodes."""
+
+        self.autofootnote_start = 1
+        """Initial auto-numbered footnote number."""
+
+        self.symbol_footnote_start = 0
+        """Initial symbol footnote symbol index."""
+
+        self.id_start = 1
+        """Initial ID number."""
+
+        self.parse_messages = []
+        """System messages generated while parsing."""
+
+        self.transform_messages = []
+        """System messages generated while applying transforms."""
+
+        import docutils.transforms
+        self.transformer = docutils.transforms.Transformer(self)
+        """Storage for transforms to be applied to this document."""
+
+        self.document = self
+
+    def asdom(self, dom=xml.dom.minidom):
+        """Return a DOM representation of this document."""
+        domroot = dom.Document()
+        domroot.appendChild(self._dom_node(domroot))
+        return domroot
+
+    def set_id(self, node, msgnode=None):
+        if node.has_key('id'):
+            id = node['id']
+            if self.ids.has_key(id) and self.ids[id] is not node:
+                msg = self.reporter.severe('Duplicate ID: "%s".' % id)
+                if msgnode != None:
+                    msgnode += msg
+        else:
+            if node.has_key('name'):
+                id = make_id(node['name'])
+            else:
+                id = ''
+            while not id or self.ids.has_key(id):
+                id = 'id%s' % self.id_start
+                self.id_start += 1
+            node['id'] = id
+        self.ids[id] = node
+        return id
+
+    def set_name_id_map(self, node, id, msgnode=None, explicit=None):
+        """
+        `self.nameids` maps names to IDs, while `self.nametypes` maps names to
+        booleans representing hyperlink type (True==explicit,
+        False==implicit).  This method updates the mappings.
+
+        The following state transition table shows how `self.nameids` ("ids")
+        and `self.nametypes` ("types") change with new input (a call to this
+        method), and what actions are performed:
+
+        ====  =====  ========  ========  =======  ====  =====  =====
+         Old State    Input          Action        New State   Notes
+        -----------  --------  -----------------  -----------  -----
+        ids   types  new type  sys.msg.  dupname  ids   types
+        ====  =====  ========  ========  =======  ====  =====  =====
+        --    --     explicit  --        --       new   True
+        --    --     implicit  --        --       new   False
+        None  False  explicit  --        --       new   True
+        old   False  explicit  implicit  old      new   True
+        None  True   explicit  explicit  new      None  True
+        old   True   explicit  explicit  new,old  None  True   [#]_
+        None  False  implicit  implicit  new      None  False
+        old   False  implicit  implicit  new,old  None  False
+        None  True   implicit  implicit  new      None  True
+        old   True   implicit  implicit  new      old   True
+        ====  =====  ========  ========  =======  ====  =====  =====
+
+        .. [#] Do not clear the name-to-id map or invalidate the old target if
+           both old and new targets are external and refer to identical URIs.
+           The new target is invalidated regardless.
+        """
+        if node.has_key('name'):
+            name = node['name']
+            if self.nameids.has_key(name):
+                self.set_duplicate_name_id(node, id, name, msgnode, explicit)
+            else:
+                self.nameids[name] = id
+                self.nametypes[name] = explicit
+
+    def set_duplicate_name_id(self, node, id, name, msgnode, explicit):
+        old_id = self.nameids[name]
+        old_explicit = self.nametypes[name]
+        self.nametypes[name] = old_explicit or explicit
+        if explicit:
+            if old_explicit:
+                level = 2
+                if old_id is not None:
+                    old_node = self.ids[old_id]
+                    if node.has_key('refuri'):
+                        refuri = node['refuri']
+                        if old_node.has_key('name') \
+                               and old_node.has_key('refuri') \
+                               and old_node['refuri'] == refuri:
+                            level = 1   # just inform if refuri's identical
+                    if level > 1:
+                        dupname(old_node)
+                        self.nameids[name] = None
+                msg = self.reporter.system_message(
+                    level, 'Duplicate explicit target name: "%s".' % name,
+                    backrefs=[id], base_node=node)
+                if msgnode != None:
+                    msgnode += msg
+                dupname(node)
+            else:
+                self.nameids[name] = id
+                if old_id is not None:
+                    old_node = self.ids[old_id]
+                    dupname(old_node)
+        else:
+            if old_id is not None and not old_explicit:
+                self.nameids[name] = None
+                old_node = self.ids[old_id]
+                dupname(old_node)
+            dupname(node)
+        if not explicit or (not old_explicit and old_id is not None):
+            msg = self.reporter.info(
+                'Duplicate implicit target name: "%s".' % name,
+                backrefs=[id], base_node=node)
+            if msgnode != None:
+                msgnode += msg
+
+    def has_name(self, name):
+        return self.nameids.has_key(name)
+
+    # "note" here is an imperative verb: "take note of".
+    def note_implicit_target(self, target, msgnode=None):
+        id = self.set_id(target, msgnode)
+        self.set_name_id_map(target, id, msgnode, explicit=None)
+
+    def note_explicit_target(self, target, msgnode=None):
+        id = self.set_id(target, msgnode)
+        self.set_name_id_map(target, id, msgnode, explicit=1)
+
+    def note_refname(self, node):
+        self.refnames.setdefault(node['refname'], []).append(node)
+
+    def note_refid(self, node):
+        self.refids.setdefault(node['refid'], []).append(node)
+
+    def note_external_target(self, target):
+        self.external_targets.append(target)
+
+    def note_internal_target(self, target):
+        self.internal_targets.append(target)
+
+    def note_indirect_target(self, target):
+        self.indirect_targets.append(target)
+        if target.has_key('name'):
+            self.note_refname(target)
+
+    def note_anonymous_target(self, target):
+        self.set_id(target)
+        self.anonymous_targets.append(target)
+
+    def note_anonymous_ref(self, ref):
+        self.anonymous_refs.append(ref)
+
+    def note_autofootnote(self, footnote):
+        self.set_id(footnote)
+        self.autofootnotes.append(footnote)
+
+    def note_autofootnote_ref(self, ref):
+        self.set_id(ref)
+        self.autofootnote_refs.append(ref)
+
+    def note_symbol_footnote(self, footnote):
+        self.set_id(footnote)
+        self.symbol_footnotes.append(footnote)
+
+    def note_symbol_footnote_ref(self, ref):
+        self.set_id(ref)
+        self.symbol_footnote_refs.append(ref)
+
+    def note_footnote(self, footnote):
+        self.set_id(footnote)
+        self.footnotes.append(footnote)
+
+    def note_footnote_ref(self, ref):
+        self.set_id(ref)
+        self.footnote_refs.setdefault(ref['refname'], []).append(ref)
+        self.note_refname(ref)
+
+    def note_citation(self, citation):
+        self.citations.append(citation)
+
+    def note_citation_ref(self, ref):
+        self.set_id(ref)
+        self.citation_refs.setdefault(ref['refname'], []).append(ref)
+        self.note_refname(ref)
+
+    def note_substitution_def(self, subdef, def_name, msgnode=None):
+        name = subdef['name'] = whitespace_normalize_name(def_name)
+        if self.substitution_defs.has_key(name):
+            msg = self.reporter.error(
+                  'Duplicate substitution definition name: "%s".' % name,
+                  base_node=subdef)
+            if msgnode != None:
+                msgnode += msg
+            oldnode = self.substitution_defs[name]
+            dupname(oldnode)
+        # keep only the last definition:
+        self.substitution_defs[name] = subdef
+        # case-insensitive mapping:
+        self.substitution_names[fully_normalize_name(name)] = name
+
+    def note_substitution_ref(self, subref, refname):
+        name = subref['refname'] = whitespace_normalize_name(refname)
+        self.substitution_refs.setdefault(name, []).append(subref)
+
+    def note_pending(self, pending, priority=None):
+        self.transformer.add_pending(pending, priority)
+
+    def note_parse_message(self, message):
+        self.parse_messages.append(message)
+
+    def note_transform_message(self, message):
+        self.transform_messages.append(message)
+
+    def note_source(self, source, offset):
+        self.current_source = source
+        if offset is None:
+            self.current_line = offset
+        else:
+            self.current_line = offset + 1
+
+    def copy(self):
+        return self.__class__(self.settings, self.reporter,
+                              **self.attributes)
+
+
+# ================
+#  Title Elements
+# ================
+
+class title(Titular, PreBibliographic, TextElement): pass
+class subtitle(Titular, PreBibliographic, TextElement): pass
+class rubric(Titular, TextElement): pass
+
+
+# ========================
+#  Bibliographic Elements
+# ========================
+
+class docinfo(Bibliographic, Element): pass
+class author(Bibliographic, TextElement): pass
+class authors(Bibliographic, Element): pass
+class organization(Bibliographic, TextElement): pass
+class address(Bibliographic, FixedTextElement): pass
+class contact(Bibliographic, TextElement): pass
+class version(Bibliographic, TextElement): pass
+class revision(Bibliographic, TextElement): pass
+class status(Bibliographic, TextElement): pass
+class date(Bibliographic, TextElement): pass
+class copyright(Bibliographic, TextElement): pass
+
+
+# =====================
+#  Decorative Elements
+# =====================
+
+class decoration(Decorative, Element): pass
+class header(Decorative, Element): pass
+class footer(Decorative, Element): pass
+
+
+# =====================
+#  Structural Elements
+# =====================
+
+class section(Structural, Element): pass
+
+
+class topic(Structural, Element):
+
+    """
+    Topics are terminal, "leaf" mini-sections, like block quotes with titles,
+    or textual figures.  A topic is just like a section, except that it has no
+    subsections, and it doesn't have to conform to section placement rules.
+
+    Topics are allowed wherever body elements (list, table, etc.) are allowed,
+    but only at the top level of a section or document.  Topics cannot nest
+    inside topics, sidebars, or body elements; you can't have a topic inside a
+    table, list, block quote, etc.
+    """
+
+
+class sidebar(Structural, Element):
+
+    """
+    Sidebars are like miniature, parallel documents that occur inside other
+    documents, providing related or reference material.  A sidebar is
+    typically offset by a border and "floats" to the side of the page; the
+    document's main text may flow around it.  Sidebars can also be likened to
+    super-footnotes; their content is outside of the flow of the document's
+    main text.
+
+    Sidebars are allowed wherever body elements (list, table, etc.) are
+    allowed, but only at the top level of a section or document.  Sidebars
+    cannot nest inside sidebars, topics, or body elements; you can't have a
+    sidebar inside a table, list, block quote, etc.
+    """
+
+
+class transition(Structural, Element): pass
+
+
+# ===============
+#  Body Elements
+# ===============
+
+class paragraph(General, TextElement): pass
+class compound(General, Element): pass
+class bullet_list(Sequential, Element): pass
+class enumerated_list(Sequential, Element): pass
+class list_item(Part, Element): pass
+class definition_list(Sequential, Element): pass
+class definition_list_item(Part, Element): pass
+class term(Part, TextElement): pass
+class classifier(Part, TextElement): pass
+class definition(Part, Element): pass
+class field_list(Sequential, Element): pass
+class field(Part, Element): pass
+class field_name(Part, TextElement): pass
+class field_body(Part, Element): pass
+
+
+class option(Part, Element):
+
+    child_text_separator = ''
+
+
+class option_argument(Part, TextElement):
+
+    def astext(self):
+        return self.get('delimiter', ' ') + TextElement.astext(self)
+
+
+class option_group(Part, Element):
+
+    child_text_separator = ', '
+
+
+class option_list(Sequential, Element): pass
+
+
+class option_list_item(Part, Element):
+
+    child_text_separator = '  '
+
+
+class option_string(Part, TextElement): pass
+class description(Part, Element): pass
+class literal_block(General, FixedTextElement): pass
+class doctest_block(General, FixedTextElement): pass
+class line_block(General, Element): pass
+
+
+class line(General, TextElement):
+
+    indent = None
+
+
+class block_quote(General, Element): pass
+class attribution(Part, TextElement): pass
+class attention(Admonition, Element): pass
+class caution(Admonition, Element): pass
+class danger(Admonition, Element): pass
+class error(Admonition, Element): pass
+class important(Admonition, Element): pass
+class note(Admonition, Element): pass
+class tip(Admonition, Element): pass
+class hint(Admonition, Element): pass
+class warning(Admonition, Element): pass
+class admonition(Admonition, Element): pass
+class comment(Special, Invisible, FixedTextElement): pass
+class substitution_definition(Special, Invisible, TextElement): pass
+class target(Special, Invisible, Inline, TextElement, Targetable): pass
+class footnote(General, Element, Labeled, BackLinkable): pass
+class citation(General, Element, Labeled, BackLinkable): pass
+class label(Part, TextElement): pass
+class figure(General, Element): pass
+class caption(Part, TextElement): pass
+class legend(Part, Element): pass
+class table(General, Element): pass
+class tgroup(Part, Element): pass
+class colspec(Part, Element): pass
+class thead(Part, Element): pass
+class tbody(Part, Element): pass
+class row(Part, Element): pass
+class entry(Part, Element): pass
+
+
+class system_message(Special, PreBibliographic, Element, BackLinkable):
+
+    def __init__(self, message=None, *children, **attributes):
+        if message:
+            p = paragraph('', message)
+            children = (p,) + children
+        try:
+            Element.__init__(self, '', *children, **attributes)
+        except:
+            print 'system_message: children=%r' % (children,)
+            raise
+
+    def astext(self):
+        line = self.get('line', '')
+        return u'%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
+                                       self['level'], Element.astext(self))
+
+
+class pending(Special, Invisible, Element):
+
+    """
+    The "pending" element is used to encapsulate a pending operation: the
+    operation (transform), the point at which to apply it, and any data it
+    requires.  Only the pending operation's location within the document is
+    stored in the public document tree (by the "pending" object itself); the
+    operation and its data are stored in the "pending" object's internal
+    instance attributes.
+
+    For example, say you want a table of contents in your reStructuredText
+    document.  The easiest way to specify where to put it is from within the
+    document, with a directive::
+
+        .. contents::
+
+    But the "contents" directive can't do its work until the entire document
+    has been parsed and possibly transformed to some extent.  So the directive
+    code leaves a placeholder behind that will trigger the second phase of its
+    processing, something like this::
+
+        <pending ...public attributes...> + internal attributes
+
+    Use `document.note_pending()` so that the
+    `docutils.transforms.Transformer` stage of processing can run all pending
+    transforms.
+    """
+
+    def __init__(self, transform, details=None,
+                 rawsource='', *children, **attributes):
+        Element.__init__(self, rawsource, *children, **attributes)
+
+        self.transform = transform
+        """The `docutils.transforms.Transform` class implementing the pending
+        operation."""
+
+        self.details = details or {}
+        """Detail data (dictionary) required by the pending operation."""
+
+    def pformat(self, indent='    ', level=0):
+        internals = [
+              '.. internal attributes:',
+              '     .transform: %s.%s' % (self.transform.__module__,
+                                          self.transform.__name__),
+              '     .details:']
+        details = self.details.items()
+        details.sort()
+        for key, value in details:
+            if isinstance(value, Node):
+                internals.append('%7s%s:' % ('', key))
+                internals.extend(['%9s%s' % ('', line)
+                                  for line in value.pformat().splitlines()])
+            elif value and isinstance(value, ListType) \
+                  and isinstance(value[0], Node):
+                internals.append('%7s%s:' % ('', key))
+                for v in value:
+                    internals.extend(['%9s%s' % ('', line)
+                                      for line in v.pformat().splitlines()])
+            else:
+                internals.append('%7s%s: %r' % ('', key, value))
+        return (Element.pformat(self, indent, level)
+                + ''.join([('    %s%s\n' % (indent * level, line))
+                           for line in internals]))
+
+    def copy(self):
+        return self.__class__(self.transform, self.details, self.rawsource,
+                              **self.attributes)
+
+
+class raw(Special, Inline, PreBibliographic, FixedTextElement):
+
+    """
+    Raw data that is to be passed untouched to the Writer.
+    """
+
+    pass
+
+
+# =================
+#  Inline Elements
+# =================
+
+class emphasis(Inline, TextElement): pass
+class strong(Inline, TextElement): pass
+class literal(Inline, TextElement): pass
+class reference(General, Inline, Referential, TextElement): pass
+class footnote_reference(Inline, Referential, TextElement): pass
+class citation_reference(Inline, Referential, TextElement): pass
+class substitution_reference(Inline, TextElement): pass
+class title_reference(Inline, TextElement): pass
+class abbreviation(Inline, TextElement): pass
+class acronym(Inline, TextElement): pass
+class superscript(Inline, TextElement): pass
+class subscript(Inline, TextElement): pass
+
+
+class image(General, Inline, TextElement):
+
+    def astext(self):
+        return self.get('alt', '')
+
+
+class inline(Inline, TextElement): pass
+class problematic(Inline, TextElement): pass
+class generated(Inline, TextElement): pass
+
+
+# ========================================
+#  Auxiliary Classes, Functions, and Data
+# ========================================
+
+node_class_names = """
+    Text
+    abbreviation acronym address admonition attention attribution author
+        authors
+    block_quote bullet_list
+    caption caution citation citation_reference classifier colspec comment
+        compound contact copyright
+    danger date decoration definition definition_list definition_list_item
+        description docinfo doctest_block document
+    emphasis entry enumerated_list error
+    field field_body field_list field_name figure footer
+        footnote footnote_reference
+    generated
+    header hint
+    image important inline
+    label legend line line_block list_item literal literal_block
+    note
+    option option_argument option_group option_list option_list_item
+        option_string organization
+    paragraph pending problematic
+    raw reference revision row rubric
+    section sidebar status strong subscript substitution_definition
+        substitution_reference subtitle superscript system_message
+    table target tbody term tgroup thead tip title title_reference topic
+        transition
+    version
+    warning""".split()
+"""A list of names of all concrete Node subclasses."""
+
+
+class NodeVisitor:
+
+    """
+    "Visitor" pattern [GoF95]_ abstract superclass implementation for
+    document tree traversals.
+
+    Each node class has corresponding methods, doing nothing by
+    default; override individual methods for specific and useful
+    behaviour.  The `dispatch_visit()` method is called by
+    `Node.walk()` upon entering a node.  `Node.walkabout()` also calls
+    the `dispatch_departure()` method before exiting a node.
+
+    The dispatch methods call "``visit_`` + node class name" or
+    "``depart_`` + node class name", resp.
+
+    This is a base class for visitors whose ``visit_...`` & ``depart_...``
+    methods should be implemented for *all* node types encountered (such as
+    for `docutils.writers.Writer` subclasses).  Unimplemented methods will
+    raise exceptions.
+
+    For sparse traversals, where only certain node types are of interest,
+    subclass `SparseNodeVisitor` instead.  When (mostly or entirely) uniform
+    processing is desired, subclass `GenericNodeVisitor`.
+
+    .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
+       Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
+       1995.
+    """
+
+    optional = ()
+    """
+    Tuple containing node class names (as strings).
+
+    No exception will be raised if writers do not implement visit
+    or departure functions for these node classes.
+
+    Used to ensure transitional compatibility with existing 3rd-party writers.
+    """
+
+    def __init__(self, document):
+        self.document = document
+
+    def dispatch_visit(self, node):
+        """
+        Call self."``visit_`` + node class name" with `node` as
+        parameter.  If the ``visit_...`` method does not exist, call
+        self.unknown_visit.
+        """
+        node_name = node.__class__.__name__
+        method = getattr(self, 'visit_' + node_name, self.unknown_visit)
+        self.document.reporter.debug(
+            'calling %s for %s' % (method.__name__, node_name),
+            category='nodes.NodeVisitor.dispatch_visit')
+        return method(node)
+
+    def dispatch_departure(self, node):
+        """
+        Call self."``depart_`` + node class name" with `node` as
+        parameter.  If the ``depart_...`` method does not exist, call
+        self.unknown_departure.
+        """
+        node_name = node.__class__.__name__
+        method = getattr(self, 'depart_' + node_name, self.unknown_departure)
+        self.document.reporter.debug(
+            'calling %s for %s' % (method.__name__, node_name),
+            category='nodes.NodeVisitor.dispatch_departure')
+        return method(node)
+
+    def unknown_visit(self, node):
+        """
+        Called when entering unknown `Node` types.
+
+        Raise an exception unless overridden.
+        """
+        if  (node.document.settings.strict_visitor
+             or node.__class__.__name__ not in self.optional):
+            raise NotImplementedError(
+                '%s visiting unknown node type: %s'
+                % (self.__class__, node.__class__.__name__))
+
+    def unknown_departure(self, node):
+        """
+        Called before exiting unknown `Node` types.
+
+        Raise exception unless overridden.
+        """
+        if  (node.document.settings.strict_visitor
+             or node.__class__.__name__ not in self.optional):
+            raise NotImplementedError(
+                '%s departing unknown node type: %s'
+                % (self.__class__, node.__class__.__name__))
+
+
+class SparseNodeVisitor(NodeVisitor):
+
+    """
+    Base class for sparse traversals, where only certain node types are of
+    interest.  When ``visit_...`` & ``depart_...`` methods should be
+    implemented for *all* node types (such as for `docutils.writers.Writer`
+    subclasses), subclass `NodeVisitor` instead.
+    """
+
+class GenericNodeVisitor(NodeVisitor):
+
+    """
+    Generic "Visitor" abstract superclass, for simple traversals.
+
+    Unless overridden, each ``visit_...`` method calls `default_visit()`, and
+    each ``depart_...`` method (when using `Node.walkabout()`) calls
+    `default_departure()`. `default_visit()` (and `default_departure()`) must
+    be overridden in subclasses.
+
+    Define fully generic visitors by overriding `default_visit()` (and
+    `default_departure()`) only. Define semi-generic visitors by overriding
+    individual ``visit_...()`` (and ``depart_...()``) methods also.
+
+    `NodeVisitor.unknown_visit()` (`NodeVisitor.unknown_departure()`) should
+    be overridden for default behavior.
+    """
+
+    def default_visit(self, node):
+        """Override for generic, uniform traversals."""
+        raise NotImplementedError
+
+    def default_departure(self, node):
+        """Override for generic, uniform traversals."""
+        raise NotImplementedError
+
+def _call_default_visit(self, node):
+    self.default_visit(node)
+
+def _call_default_departure(self, node):
+    self.default_departure(node)
+
+def _nop(self, node):
+    pass
+
+def _add_node_class_names(names):
+    """Save typing with dynamic assignments:"""
+    for _name in names:
+        setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit)
+        setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure)
+        setattr(SparseNodeVisitor, 'visit_' + _name, _nop)
+        setattr(SparseNodeVisitor, 'depart' + _name, _nop)
+
+_add_node_class_names(node_class_names)
+
+class TreeCopyVisitor(GenericNodeVisitor):
+
+    """
+    Make a complete copy of a tree or branch, including element attributes.
+    """
+
+    def __init__(self, document):
+        GenericNodeVisitor.__init__(self, document)
+        self.parent_stack = []
+        self.parent = []
+
+    def get_tree_copy(self):
+        return self.parent[0]
+
+    def default_visit(self, node):
+        """Copy the current node, and make it the new acting parent."""
+        newnode = node.copy()
+        self.parent.append(newnode)
+        self.parent_stack.append(self.parent)
+        self.parent = newnode
+
+    def default_departure(self, node):
+        """Restore the previous acting parent."""
+        self.parent = self.parent_stack.pop()
+
+
+class TreePruningException(Exception):
+
+    """
+    Base class for `NodeVisitor`-related tree pruning exceptions.
+
+    Raise subclasses from within ``visit_...`` or ``depart_...`` methods
+    called from `Node.walk()` and `Node.walkabout()` tree traversals to prune
+    the tree traversed.
+    """
+
+    pass
+
+
+class SkipChildren(TreePruningException):
+
+    """
+    Do not visit any children of the current node.  The current node's
+    siblings and ``depart_...`` method are not affected.
+    """
+
+    pass
+
+
+class SkipSiblings(TreePruningException):
+
+    """
+    Do not visit any more siblings (to the right) of the current node.  The
+    current node's children and its ``depart_...`` method are not affected.
+    """
+
+    pass
+
+
+class SkipNode(TreePruningException):
+
+    """
+    Do not visit the current node's children, and do not call the current
+    node's ``depart_...`` method.
+    """
+
+    pass
+
+
+class SkipDeparture(TreePruningException):
+
+    """
+    Do not call the current node's ``depart_...`` method.  The current node's
+    children and siblings are not affected.
+    """
+
+    pass
+
+
+class NodeFound(TreePruningException):
+
+    """
+    Raise to indicate that the target of a search has been found.  This
+    exception must be caught by the client; it is not caught by the traversal
+    code.
+    """
+
+    pass
+
+
+def make_id(string):
+    """
+    Convert `string` into an identifier and return it.
+
+    Docutils identifiers will conform to the regular expression
+    ``[a-z](-?[a-z0-9]+)*``.  For CSS compatibility, identifiers (the "class"
+    and "id" attributes) should have no underscores, colons, or periods.
+    Hyphens may be used.
+
+    - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens:
+
+          ID and NAME tokens must begin with a letter ([A-Za-z]) and may be
+          followed by any number of letters, digits ([0-9]), hyphens ("-"),
+          underscores ("_"), colons (":"), and periods (".").
+
+    - However the `CSS1 spec`_ defines identifiers based on the "name" token,
+      a tighter interpretation ("flex" tokenizer notation; "latin1" and
+      "escape" 8-bit characters have been replaced with entities)::
+
+          unicode     \\[0-9a-f]{1,4}
+          latin1      [&iexcl;-&yuml;]
+          escape      {unicode}|\\[ -~&iexcl;-&yuml;]
+          nmchar      [-a-z0-9]|{latin1}|{escape}
+          name        {nmchar}+
+
+    The CSS1 "nmchar" rule does not include underscores ("_"), colons (":"),
+    or periods ("."), therefore "class" and "id" attributes should not contain
+    these characters. They should be replaced with hyphens ("-"). Combined
+    with HTML's requirements (the first character must be a letter; no
+    "unicode", "latin1", or "escape" characters), this results in the
+    ``[a-z](-?[a-z0-9]+)*`` pattern.
+
+    .. _HTML 4.01 spec: http://www.w3.org/TR/html401
+    .. _CSS1 spec: http://www.w3.org/TR/REC-CSS1
+    """
+    id = _non_id_chars.sub('-', ' '.join(string.lower().split()))
+    id = _non_id_at_ends.sub('', id)
+    return str(id)
+
+_non_id_chars = re.compile('[^a-z0-9]+')
+_non_id_at_ends = re.compile('^[-0-9]+|-+$')
+
+def dupname(node):
+    node['dupname'] = node['name']
+    del node['name']
+
+def fully_normalize_name(name):
+    """Return a case- and whitespace-normalized name."""
+    return ' '.join(name.lower().split())
+
+def whitespace_normalize_name(name):
+    """Return a whitespace-normalized name."""
+    return ' '.join(name.split())

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,49 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:03 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This package contains Docutils parser modules.
+"""
+
+__docformat__ = 'reStructuredText'
+
+from docutils import Component
+
+
+class Parser(Component):
+
+    component_type = 'parser'
+    config_section = 'parsers'
+
+    def parse(self, inputstring, document):
+        """Override to parse `inputstring` into document tree `document`."""
+        raise NotImplementedError('subclass must override this method')
+
+    def setup_parse(self, inputstring, document):
+        """Initial parse setup.  Call at start of `self.parse()`."""
+        self.inputstring = inputstring
+        self.document = document
+        document.reporter.attach_observer(document.note_parse_message)
+
+    def finish_parse(self):
+        """Finalize parse details.  Call at end of `self.parse()`."""
+        self.document.reporter.detach_observer(
+            self.document.note_parse_message)
+
+
+_parser_aliases = {
+      'restructuredtext': 'rst',
+      'rest': 'rst',
+      'restx': 'rst',
+      'rtxt': 'rst',}
+
+def get_parser_class(parser_name):
+    """Return the Parser class from the `parser_name` module."""
+    parser_name = parser_name.lower()
+    if _parser_aliases.has_key(parser_name):
+        parser_name = _parser_aliases[parser_name]
+    module = __import__(parser_name, globals(), locals())
+    return module.Parser

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,140 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:03 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This is ``docutils.parsers.rst`` package. It exports a single class, `Parser`,
+the reStructuredText parser.
+
+
+Usage
+=====
+
+1. Create a parser::
+
+       parser = docutils.parsers.rst.Parser()
+
+   Several optional arguments may be passed to modify the parser's behavior.
+   Please see `Customizing the Parser`_ below for details.
+
+2. Gather input (a multi-line string), by reading a file or the standard
+   input::
+
+       input = sys.stdin.read()
+
+3. Create a new empty `docutils.nodes.document` tree::
+
+       document = docutils.utils.new_document(source, settings)
+
+   See `docutils.utils.new_document()` for parameter details.
+
+4. Run the parser, populating the document tree::
+
+       parser.parse(input, document)
+
+
+Parser Overview
+===============
+
+The reStructuredText parser is implemented as a state machine, examining its
+input one line at a time. To understand how the parser works, please first
+become familiar with the `docutils.statemachine` module, then see the
+`states` module.
+
+
+Customizing the Parser
+----------------------
+
+Anything that isn't already customizable is that way simply because that type
+of customizability hasn't been implemented yet.  Patches welcome!
+
+When instantiating an object of the `Parser` class, two parameters may be
+passed: ``rfc2822`` and ``inliner``.  Pass ``rfc2822=1`` to enable an initial
+RFC-2822 style header block, parsed as a "field_list" element (with "class"
+attribute set to "rfc2822").  Currently this is the only body-level element
+which is customizable without subclassing.  (Tip: subclass `Parser` and change
+its "state_classes" and "initial_state" attributes to refer to new classes.
+Contact the author if you need more details.)
+
+The ``inliner`` parameter takes an instance of `states.Inliner` or a subclass.
+It handles inline markup recognition.  A common extension is the addition of
+further implicit hyperlinks, like "RFC 2822".  This can be done by subclassing
+`states.Inliner`, adding a new method for the implicit markup, and adding a
+``(pattern, method)`` pair to the "implicit_dispatch" attribute of the
+subclass.  See `states.Inliner.implicit_inline()` for details.  Explicit
+inline markup can be customized in a `states.Inliner` subclass via the
+``patterns.initial`` and ``dispatch`` attributes (and new methods as
+appropriate).
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import docutils.parsers
+import docutils.statemachine
+from docutils.parsers.rst import states
+from docutils import frontend
+
+
+class Parser(docutils.parsers.Parser):
+
+    """The reStructuredText parser."""
+
+    supported = ('restructuredtext', 'rst', 'rest', 'restx', 'rtxt', 'rstx')
+    """Aliases this parser supports."""
+
+    settings_spec = (
+        'reStructuredText Parser Options',
+        None,
+        (('Recognize and link to standalone PEP references (like "PEP 258").',
+          ['--pep-references'],
+          {'action': 'store_true', 'validator': frontend.validate_boolean}),
+         ('Base URL for PEP references '
+          '(default "http://www.python.org/peps/").',
+          ['--pep-base-url'],
+          {'metavar': '<URL>', 'default': 'http://www.python.org/peps/',
+           'validator': frontend.validate_url_trailing_slash}),
+         ('Recognize and link to standalone RFC references (like "RFC 822").',
+          ['--rfc-references'],
+          {'action': 'store_true', 'validator': frontend.validate_boolean}),
+         ('Base URL for RFC references (default "http://www.faqs.org/rfcs/").',
+          ['--rfc-base-url'],
+          {'metavar': '<URL>', 'default': 'http://www.faqs.org/rfcs/',
+           'validator': frontend.validate_url_trailing_slash}),
+         ('Set number of spaces for tab expansion (default 8).',
+          ['--tab-width'],
+          {'metavar': '<width>', 'type': 'int', 'default': 8}),
+         ('Remove spaces before footnote references.',
+          ['--trim-footnote-reference-space'],
+          {'action': 'store_true', 'validator': frontend.validate_boolean}),
+         ('Leave spaces before footnote references.',
+          ['--leave-footnote-reference-space'],
+          {'action': 'store_false', 'dest': 'trim_footnote_reference_space',
+           'validator': frontend.validate_boolean}),))
+
+    config_section = 'restructuredtext parser'
+    config_section_dependencies = ('parsers',)
+
+    def __init__(self, rfc2822=None, inliner=None):
+        if rfc2822:
+            self.initial_state = 'RFC2822Body'
+        else:
+            self.initial_state = 'Body'
+        self.state_classes = states.state_classes
+        self.inliner = inliner
+
+    def parse(self, inputstring, document):
+        """Parse `inputstring` and populate `document`, a document tree."""
+        self.setup_parse(inputstring, document)
+        debug = document.reporter[''].debug
+        self.statemachine = states.RSTStateMachine(
+              state_classes=self.state_classes,
+              initial_state=self.initial_state,
+              debug=debug)
+        inputlines = docutils.statemachine.string2lines(
+              inputstring, tab_width=document.settings.tab_width,
+              convert_whitespace=1)
+        self.statemachine.run(inputlines, document, inliner=self.inliner)
+        self.finish_parse()

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,381 @@
+# Author: David Goodger
+# Contact: goodger at python.org
+# Revision: $Revision: 1.2.10.8 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This package contains directive implementation modules.
+
+The interface for directive functions is as follows::
+
+    def directive_fn(name, arguments, options, content, lineno,
+                     content_offset, block_text, state, state_machine):
+        code...
+
+    # Set function attributes:
+    directive_fn.arguments = ...
+    directive_fn.options = ...
+    direcitve_fn.content = ...
+
+Parameters:
+
+- ``name`` is the directive type or name (string).
+
+- ``arguments`` is a list of positional arguments (strings).
+
+- ``options`` is a dictionary mapping option names (strings) to values (type
+  depends on option conversion functions; see below).
+
+- ``content`` is a list of strings, the directive content.
+
+- ``lineno`` is the line number of the first line of the directive.
+
+- ``content_offset`` is the line offset of the first line of the content from
+  the beginning of the current input.  Used when initiating a nested parse.
+
+- ``block_text`` is a string containing the entire directive.  Include it as
+  the content of a literal block in a system message if there is a problem.
+
+- ``state`` is the state which called the directive function.
+
+- ``state_machine`` is the state machine which controls the state which called
+  the directive function.
+
+Function attributes, interpreted by the directive parser (which calls the
+directive function):
+
+- ``arguments``: A 3-tuple specifying the expected positional arguments, or
+  ``None`` if the directive has no arguments.  The 3 items in the tuple are
+  ``(required, optional, whitespace OK in last argument)``:
+
+  1. The number of required arguments.
+  2. The number of optional arguments.
+  3. A boolean, indicating if the final argument may contain whitespace.
+
+  Arguments are normally single whitespace-separated words.  The final
+  argument may contain whitespace if the third item in the argument spec tuple
+  is 1/True.  If the form of the arguments is more complex, specify only one
+  argument (either required or optional) and indicate that final whitespace is
+  OK; the client code must do any context-sensitive parsing.
+
+- ``options``: A dictionary, mapping known option names to conversion
+  functions such as `int` or `float`.  ``None`` or an empty dict implies no
+  options to parse.  Several directive option conversion functions are defined
+  in this module.
+
+  Option conversion functions take a single parameter, the option argument (a
+  string or ``None``), validate it and/or convert it to the appropriate form.
+  Conversion functions may raise ``ValueError`` and ``TypeError`` exceptions.
+
+- ``content``: A boolean; true if content is allowed.  Client code must handle
+  the case where content is required but not supplied (an empty content list
+  will be supplied).
+
+Directive functions return a list of nodes which will be inserted into the
+document tree at the point where the directive was encountered (can be an
+empty list).
+
+See `Creating reStructuredText Directives`_ for more information.
+
+.. _Creating reStructuredText Directives:
+   http://docutils.sourceforge.net/docs/howto/rst-directives.html
+"""
+
+__docformat__ = 'reStructuredText'
+
+import re
+import codecs
+from docutils import nodes
+from docutils.parsers.rst.languages import en as _fallback_language_module
+
+
+_directive_registry = {
+      'attention': ('admonitions', 'attention'),
+      'caution': ('admonitions', 'caution'),
+      'danger': ('admonitions', 'danger'),
+      'error': ('admonitions', 'error'),
+      'important': ('admonitions', 'important'),
+      'note': ('admonitions', 'note'),
+      'tip': ('admonitions', 'tip'),
+      'hint': ('admonitions', 'hint'),
+      'warning': ('admonitions', 'warning'),
+      'admonition': ('admonitions', 'admonition'),
+      'sidebar': ('body', 'sidebar'),
+      'topic': ('body', 'topic'),
+      'line-block': ('body', 'line_block'),
+      'parsed-literal': ('body', 'parsed_literal'),
+      'rubric': ('body', 'rubric'),
+      'epigraph': ('body', 'epigraph'),
+      'highlights': ('body', 'highlights'),
+      'pull-quote': ('body', 'pull_quote'),
+      'compound': ('body', 'compound'),
+      #'questions': ('body', 'question_list'),
+      'table': ('tables', 'table'),
+      'csv-table': ('tables', 'csv_table'),
+      'image': ('images', 'image'),
+      'figure': ('images', 'figure'),
+      'contents': ('parts', 'contents'),
+      'sectnum': ('parts', 'sectnum'),
+      #'footnotes': ('parts', 'footnotes'),
+      #'citations': ('parts', 'citations'),
+      'target-notes': ('references', 'target_notes'),
+      'meta': ('html', 'meta'),
+      #'imagemap': ('html', 'imagemap'),
+      'raw': ('misc', 'raw'),
+      'include': ('misc', 'include'),
+      'replace': ('misc', 'replace'),
+      'unicode': ('misc', 'unicode_directive'),
+      'class': ('misc', 'class_directive'),
+      'role': ('misc', 'role'),
+      'restructuredtext-test-directive': ('misc', 'directive_test_function'),}
+"""Mapping of directive name to (module name, function name).  The directive
+name is canonical & must be lowercase.  Language-dependent names are defined
+in the ``language`` subpackage."""
+
+_modules = {}
+"""Cache of imported directive modules."""
+
+_directives = {}
+"""Cache of imported directive functions."""
+
+def directive(directive_name, language_module, document):
+    """
+    Locate and return a directive function from its language-dependent name.
+    If not found in the current language, check English.  Return None if the
+    named directive cannot be found.
+    """
+    normname = directive_name.lower()
+    messages = []
+    msg_text = []
+    if _directives.has_key(normname):
+        return _directives[normname], messages
+    canonicalname = None
+    try:
+        canonicalname = language_module.directives[normname]
+    except AttributeError, error:
+        msg_text.append('Problem retrieving directive entry from language '
+                        'module %r: %s.' % (language_module, error))
+    except KeyError:
+        msg_text.append('No directive entry for "%s" in module "%s".'
+                        % (directive_name, language_module.__name__))
+    if not canonicalname:
+        try:
+            canonicalname = _fallback_language_module.directives[normname]
+            msg_text.append('Using English fallback for directive "%s".'
+                            % directive_name)
+        except KeyError:
+            msg_text.append('Trying "%s" as canonical directive name.'
+                            % directive_name)
+            # The canonical name should be an English name, but just in case:
+            canonicalname = normname
+    if msg_text:
+        message = document.reporter.info(
+            '\n'.join(msg_text), line=document.current_line)
+        messages.append(message)
+    try:
+        modulename, functionname = _directive_registry[canonicalname]
+    except KeyError:
+        messages.append(document.reporter.error(
+            'Directive "%s" not registered (canonical name "%s").'
+            % (directive_name, canonicalname), line=document.current_line))
+        return None, messages
+    if _modules.has_key(modulename):
+        module = _modules[modulename]
+    else:
+        try:
+            module = __import__(modulename, globals(), locals())
+        except ImportError, detail:
+            messages.append(document.reporter.error(
+                'Error importing directive module "%s" (directive "%s"):\n%s'
+                % (modulename, directive_name, detail),
+                line=document.current_line))
+            return None, messages
+    try:
+        function = getattr(module, functionname)
+        _directives[normname] = function
+    except AttributeError:
+        messages.append(document.reporter.error(
+            'No function "%s" in module "%s" (directive "%s").'
+            % (functionname, modulename, directive_name),
+            line=document.current_line))
+        return None, messages
+    return function, messages
+
+def register_directive(name, directive_function):
+    """
+    Register a nonstandard application-defined directive function.
+    Language lookups are not needed for such functions.
+    """
+    _directives[name] = directive_function
+
+def flag(argument):
+    """
+    Check for a valid flag option (no argument) and return ``None``.
+    (Directive option conversion function.)
+
+    Raise ``ValueError`` if an argument is found.
+    """
+    if argument and argument.strip():
+        raise ValueError('no argument is allowed; "%s" supplied' % argument)
+    else:
+        return None
+
+def unchanged_required(argument):
+    """
+    Return the argument text, unchanged.
+    (Directive option conversion function.)
+
+    Raise ``ValueError`` if no argument is found.
+    """
+    if argument is None:
+        raise ValueError('argument required but none supplied')
+    else:
+        return argument  # unchanged!
+
+def unchanged(argument):
+    """
+    Return the argument text, unchanged.
+    (Directive option conversion function.)
+
+    No argument implies empty string ("").
+    """
+    if argument is None:
+        return u''
+    else:
+        return argument  # unchanged!
+
+def path(argument):
+    """
+    Return the path argument unwrapped (with newlines removed).
+    (Directive option conversion function.)
+
+    Raise ``ValueError`` if no argument is found or if the path contains
+    internal whitespace.
+    """
+    if argument is None:
+        raise ValueError('argument required but none supplied')
+    else:
+        path = ''.join([s.strip() for s in argument.splitlines()])
+        if path.find(' ') == -1:
+            return path
+        else:
+            raise ValueError('path contains whitespace')
+
+def nonnegative_int(argument):
+    """
+    Check for a nonnegative integer argument; raise ``ValueError`` if not.
+    (Directive option conversion function.)
+    """
+    value = int(argument)
+    if value < 0:
+        raise ValueError('negative value; must be positive or zero')
+    return value
+
+def class_option(argument):
+    """
+    Convert the argument into an ID-compatible string and return it.
+    (Directive option conversion function.)
+
+    Raise ``ValueError`` if no argument is found.
+    """
+    if argument is None:
+        raise ValueError('argument required but none supplied')
+    names = argument.split()
+    class_names = []
+    for name in names:
+        class_name = nodes.make_id(name)
+        if not class_name:
+            raise ValueError('cannot make "%s" into a class name' % name)
+        class_names.append(class_name)
+    return ' '.join(class_names)
+
+unicode_pattern = re.compile(
+    r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE)
+
+def unicode_code(code):
+    r"""
+    Convert a Unicode character code to a Unicode character.
+
+    Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``,
+    ``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style
+    numeric character entities (e.g. ``&#x262E;``).  Other text remains as-is.
+    """
+    try:
+        if code.isdigit():                  # decimal number
+            return unichr(int(code))
+        else:
+            match = unicode_pattern.match(code)
+            if match:                       # hex number
+                value = match.group(1) or match.group(2)
+                return unichr(int(value, 16))
+            else:                           # other text
+                return code
+    except OverflowError, detail:
+        raise ValueError('code too large (%s)' % detail)
+
+def single_char_or_unicode(argument):
+    char = unicode_code(argument)
+    if len(char) > 1:
+        raise ValueError('%r invalid; must be a single character or '
+                         'a Unicode code' % char)
+    return char
+
+def single_char_or_whitespace_or_unicode(argument):
+    if argument == 'tab':
+        char = '\t'
+    elif argument == 'space':
+        char = ' '
+    else:
+        char = single_char_or_unicode(argument)
+    return char
+
+def positive_int(argument):
+    value = int(argument)
+    if value < 1:
+        raise ValueError('negative or zero value; must be positive')
+    return value
+
+def positive_int_list(argument):
+    if ',' in argument:
+        entries = argument.split(',')
+    else:
+        entries = argument.split()
+    return [positive_int(entry) for entry in entries]
+
+def encoding(argument):
+    try:
+        codecs.lookup(argument)
+    except LookupError:
+        raise ValueError('unknown encoding: "%s"' % argument)
+    return argument
+
+def choice(argument, values):
+    """
+    Directive option utility function, supplied to enable options whose
+    argument must be a member of a finite set of possible values (must be
+    lower case).  A custom conversion function must be written to use it.  For
+    example::
+
+        from docutils.parsers.rst import directives
+
+        def yesno(argument):
+            return directives.choice(argument, ('yes', 'no'))
+
+    Raise ``ValueError`` if no argument is found or if the argument's value is
+    not valid (not an entry in the supplied list).
+    """
+    try:
+        value = argument.lower().strip()
+    except AttributeError:
+        raise ValueError('must supply an argument; choose from %s'
+                         % format_values(values))
+    if value in values:
+        return value
+    else:
+        raise ValueError('"%s" unknown; choose from %s'
+                         % (argument, format_values(values)))
+
+def format_values(values):
+    return '%s, or "%s"' % (', '.join(['"%s"' % s for s in values[:-1]]),
+                            values[-1])

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/admonitions.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/admonitions.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/admonitions.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,90 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Admonition directives.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+from docutils.parsers.rst import states, directives
+from docutils import nodes
+
+
+def make_admonition(node_class, name, arguments, options, content, lineno,
+                       content_offset, block_text, state, state_machine):
+    if not content:
+        error = state_machine.reporter.error(
+            'The "%s" admonition is empty; content required.' % (name),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    text = '\n'.join(content)
+    admonition_node = node_class(text)
+    if arguments:
+        title_text = arguments[0]
+        textnodes, messages = state.inline_text(title_text, lineno)
+        admonition_node += nodes.title(title_text, '', *textnodes)
+        admonition_node += messages
+        if options.has_key('class'):
+            class_value = options['class']
+        else:
+            class_value = 'admonition-' + nodes.make_id(title_text)
+        admonition_node.set_class(class_value)
+    state.nested_parse(content, content_offset, admonition_node)
+    return [admonition_node]
+
+def admonition(*args):
+    return make_admonition(nodes.admonition, *args)
+
+admonition.arguments = (1, 0, 1)
+admonition.options = {'class': directives.class_option}
+admonition.content = 1
+
+def attention(*args):
+    return make_admonition(nodes.attention, *args)
+
+attention.content = 1
+
+def caution(*args):
+    return make_admonition(nodes.caution, *args)
+
+caution.content = 1
+
+def danger(*args):
+    return make_admonition(nodes.danger, *args)
+
+danger.content = 1
+
+def error(*args):
+    return make_admonition(nodes.error, *args)
+
+error.content = 1
+
+def hint(*args):
+    return make_admonition(nodes.hint, *args)
+
+hint.content = 1
+
+def important(*args):
+    return make_admonition(nodes.important, *args)
+
+important.content = 1
+
+def note(*args):
+    return make_admonition(nodes.note, *args)
+
+note.content = 1
+
+def tip(*args):
+    return make_admonition(nodes.tip, *args)
+
+tip.content = 1
+
+def warning(*args):
+    return make_admonition(nodes.warning, *args)
+
+warning.content = 1

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/body.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/body.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/body.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,163 @@
+# Author: David Goodger
+# Contact: goodger at python.org
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for additional body elements.
+
+See `docutils.parsers.rst.directives` for API details.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+from docutils import nodes
+from docutils.parsers.rst import directives
+
+              
+def topic(name, arguments, options, content, lineno,
+          content_offset, block_text, state, state_machine,
+          node_class=nodes.topic):
+    if not state_machine.match_titles:
+        error = state_machine.reporter.error(
+              'The "%s" directive may not be used within topics, sidebars, '
+              'or body elements.' % name,
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    if not content:
+        warning = state_machine.reporter.warning(
+            'Content block expected for the "%s" directive; none found.'
+            % name, nodes.literal_block(block_text, block_text),
+            line=lineno)
+        return [warning]
+    title_text = arguments[0]
+    textnodes, messages = state.inline_text(title_text, lineno)
+    titles = [nodes.title(title_text, '', *textnodes)]
+    # sidebar uses this code
+    if options.has_key('subtitle'):
+        textnodes, more_messages = state.inline_text(options['subtitle'],
+                                                     lineno)
+        titles.append(nodes.subtitle(options['subtitle'], '', *textnodes))
+        messages.extend(more_messages)
+    text = '\n'.join(content)
+    node = node_class(text, *(titles + messages))
+    if options.has_key('class'):
+        node.set_class(options['class'])
+    if text:
+        state.nested_parse(content, content_offset, node)
+    return [node]
+
+topic.arguments = (1, 0, 1)
+topic.options = {'class': directives.class_option}
+topic.content = 1
+
+def sidebar(name, arguments, options, content, lineno,
+            content_offset, block_text, state, state_machine):
+    return topic(name, arguments, options, content, lineno,
+                 content_offset, block_text, state, state_machine,
+                 node_class=nodes.sidebar)
+
+sidebar.arguments = (1, 0, 1)
+sidebar.options = {'subtitle': directives.unchanged_required,
+                   'class': directives.class_option}
+sidebar.content = 1
+
+def line_block(name, arguments, options, content, lineno,
+               content_offset, block_text, state, state_machine):
+    if not content:
+        warning = state_machine.reporter.warning(
+            'Content block expected for the "%s" directive; none found.'
+            % name, nodes.literal_block(block_text, block_text), line=lineno)
+        return [warning]
+    block = nodes.line_block()
+    node_list = [block]
+    for line_text in content:
+        text_nodes, messages = state.inline_text(line_text.strip(),
+                                                 lineno + content_offset)
+        line = nodes.line(line_text, '', *text_nodes)
+        if line_text.strip():
+            line.indent = len(line_text) - len(line_text.lstrip())
+        block += line
+        node_list.extend(messages)
+        content_offset += 1
+    state.nest_line_block_lines(block)
+    return node_list
+
+line_block.options = {'class': directives.class_option}
+line_block.content = 1
+
+def parsed_literal(name, arguments, options, content, lineno,
+                   content_offset, block_text, state, state_machine):
+    return block(name, arguments, options, content, lineno,
+                 content_offset, block_text, state, state_machine,
+                 node_class=nodes.literal_block)
+
+parsed_literal.options = {'class': directives.class_option}
+parsed_literal.content = 1
+
+def block(name, arguments, options, content, lineno,
+          content_offset, block_text, state, state_machine, node_class):
+    if not content:
+        warning = state_machine.reporter.warning(
+            'Content block expected for the "%s" directive; none found.'
+            % name, nodes.literal_block(block_text, block_text), line=lineno)
+        return [warning]
+    text = '\n'.join(content)
+    text_nodes, messages = state.inline_text(text, lineno)
+    node = node_class(text, '', *text_nodes, **options)
+    node.line = content_offset + 1
+    return [node] + messages
+
+def rubric(name, arguments, options, content, lineno,
+             content_offset, block_text, state, state_machine):
+    rubric_text = arguments[0]
+    textnodes, messages = state.inline_text(rubric_text, lineno)
+    rubric = nodes.rubric(rubric_text, '', *textnodes, **options)
+    return [rubric] + messages
+
+rubric.arguments = (1, 0, 1)
+rubric.options = {'class': directives.class_option}
+
+def epigraph(name, arguments, options, content, lineno,
+             content_offset, block_text, state, state_machine):
+    block_quote, messages = state.block_quote(content, content_offset)
+    block_quote.set_class('epigraph')
+    return [block_quote] + messages
+
+epigraph.content = 1
+
+def highlights(name, arguments, options, content, lineno,
+             content_offset, block_text, state, state_machine):
+    block_quote, messages = state.block_quote(content, content_offset)
+    block_quote.set_class('highlights')
+    return [block_quote] + messages
+
+highlights.content = 1
+
+def pull_quote(name, arguments, options, content, lineno,
+             content_offset, block_text, state, state_machine):
+    block_quote, messages = state.block_quote(content, content_offset)
+    block_quote.set_class('pull-quote')
+    return [block_quote] + messages
+
+pull_quote.content = 1
+
+def compound(name, arguments, options, content, lineno,
+             content_offset, block_text, state, state_machine):
+    text = '\n'.join(content)
+    if not text:
+        error = state_machine.reporter.error(
+            'The "%s" directive is empty; content required.' % name,
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    node = nodes.compound(text)
+    if options.has_key('class'):
+        node.set_class(options['class'])
+    state.nested_parse(content, content_offset, node)
+    return [node]
+
+compound.options = {'class': directives.class_option}
+compound.content = 1

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/html.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/html.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/html.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,96 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for typically HTML-specific constructs.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+from docutils import nodes, utils
+from docutils.parsers.rst import states
+from docutils.transforms import components
+
+
+def meta(name, arguments, options, content, lineno,
+         content_offset, block_text, state, state_machine):
+    node = nodes.Element()
+    if content:
+        new_line_offset, blank_finish = state.nested_list_parse(
+              content, content_offset, node, initial_state='MetaBody',
+              blank_finish=1, state_machine_kwargs=metaSMkwargs)
+        if (new_line_offset - content_offset) != len(content):
+            # incomplete parse of block?
+            error = state_machine.reporter.error(
+                'Invalid meta directive.',
+                nodes.literal_block(block_text, block_text), line=lineno)
+            node += error
+    else:
+        error = state_machine.reporter.error(
+            'Empty meta directive.',
+            nodes.literal_block(block_text, block_text), line=lineno)
+        node += error
+    return node.get_children()
+
+meta.content = 1
+
+def imagemap(name, arguments, options, content, lineno,
+             content_offset, block_text, state, state_machine):
+    return []
+
+
+class MetaBody(states.SpecializedBody):
+
+    class meta(nodes.Special, nodes.PreBibliographic, nodes.Element):
+        """HTML-specific "meta" element."""
+        pass
+
+    def field_marker(self, match, context, next_state):
+        """Meta element."""
+        node, blank_finish = self.parsemeta(match)
+        self.parent += node
+        return [], next_state, []
+
+    def parsemeta(self, match):
+        name = self.parse_field_marker(match)
+        indented, indent, line_offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end())
+        node = self.meta()
+        pending = nodes.pending(components.Filter,
+                                {'component': 'writer',
+                                 'format': 'html',
+                                 'nodes': [node]})
+        node['content'] = ' '.join(indented)
+        if not indented:
+            line = self.state_machine.line
+            msg = self.reporter.info(
+                  'No content for meta tag "%s".' % name,
+                  nodes.literal_block(line, line),
+                  line=self.state_machine.abs_line_number())
+            return msg, blank_finish
+        tokens = name.split()
+        try:
+            attname, val = utils.extract_name_value(tokens[0])[0]
+            node[attname.lower()] = val
+        except utils.NameValueError:
+            node['name'] = tokens[0]
+        for token in tokens[1:]:
+            try:
+                attname, val = utils.extract_name_value(token)[0]
+                node[attname.lower()] = val
+            except utils.NameValueError, detail:
+                line = self.state_machine.line
+                msg = self.reporter.error(
+                      'Error parsing meta tag attribute "%s": %s.'
+                      % (token, detail), nodes.literal_block(line, line),
+                      line=self.state_machine.abs_line_number())
+                return msg, blank_finish
+        self.document.note_pending(pending)
+        return pending, blank_finish
+
+
+metaSMkwargs = {'state_classes': (MetaBody,)}

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/images.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/images.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/images.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,122 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for figures and simple images.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+from docutils import nodes, utils
+from docutils.parsers.rst import directives, states
+from docutils.nodes import whitespace_normalize_name
+
+try:
+    import Image                        # PIL
+except ImportError:
+    Image = None
+
+align_values = ('top', 'middle', 'bottom', 'left', 'center', 'right')
+
+def align(argument):
+    return directives.choice(argument, align_values)
+
+def image(name, arguments, options, content, lineno,
+          content_offset, block_text, state, state_machine):
+    messages = []
+    reference = ''.join(arguments[0].split('\n'))
+    if reference.find(' ') != -1:
+        error = state_machine.reporter.error(
+              'Image URI contains whitespace.',
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    options['uri'] = reference
+    reference_node = None
+    if options.has_key('target'):
+        block = states.escape2null(options['target']).splitlines()
+        block = [line for line in block]
+        target_type, data = state.parse_target(block, block_text, lineno)
+        if target_type == 'refuri':
+            reference_node = nodes.reference(refuri=data)
+        elif target_type == 'refname':
+            reference_node = nodes.reference(
+                refname=data, name=whitespace_normalize_name(options['target']))
+            state.document.note_refname(reference_node)
+        else:                           # malformed target
+            messages.append(data)       # data is a system message
+        del options['target']
+    image_node = nodes.image(block_text, **options)
+    if reference_node:
+        reference_node += image_node
+        return messages + [reference_node]
+    else:
+        return messages + [image_node]
+
+image.arguments = (1, 0, 1)
+image.options = {'alt': directives.unchanged,
+                 'height': directives.nonnegative_int,
+                 'width': directives.nonnegative_int,
+                 'scale': directives.nonnegative_int,
+                 'align': align,
+                 'target': directives.unchanged_required,
+                 'class': directives.class_option}
+
+def figure(name, arguments, options, content, lineno,
+           content_offset, block_text, state, state_machine):
+    figwidth = options.setdefault('figwidth')
+    figclass = options.setdefault('figclass')
+    del options['figwidth']
+    del options['figclass']
+    (image_node,) = image(name, arguments, options, content, lineno,
+                         content_offset, block_text, state, state_machine)
+    if isinstance(image_node, nodes.system_message):
+        return [image_node]
+    figure_node = nodes.figure('', image_node)
+    if figwidth == 'image':
+        if Image:
+            # PIL doesn't like Unicode paths:
+            try:
+                i = Image.open(str(image_node['uri']))
+            except (IOError, UnicodeError):
+                pass
+            else:
+                state.document.settings.record_dependencies.add(reference)
+                figure_node['width'] = i.size[0]
+    elif figwidth is not None:
+        figure_node['width'] = figwidth
+    if figclass:
+        figure_node.set_class(figclass)
+    if content:
+        node = nodes.Element()          # anonymous container for parsing
+        state.nested_parse(content, content_offset, node)
+        first_node = node[0]
+        if isinstance(first_node, nodes.paragraph):
+            caption = nodes.caption(first_node.rawsource, '',
+                                    *first_node.children)
+            figure_node += caption
+        elif not (isinstance(first_node, nodes.comment)
+                  and len(first_node) == 0):
+            error = state_machine.reporter.error(
+                  'Figure caption must be a paragraph or empty comment.',
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return [figure_node, error]
+        if len(node) > 1:
+            figure_node += nodes.legend('', *node[1:])
+    return [figure_node]
+
+def figwidth_value(argument):
+    if argument.lower() == 'image':
+        return 'image'
+    else:
+        return directives.nonnegative_int(argument)
+
+figure.arguments = (1, 0, 1)
+figure.options = {'figwidth': figwidth_value,
+                  'figclass': directives.class_option}
+figure.options.update(image.options)
+figure.content = 1

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/misc.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/misc.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/misc.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,322 @@
+# Authors: David Goodger, Dethe Elza
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""Miscellaneous directives."""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import os.path
+import re
+from docutils import io, nodes, statemachine, utils
+from docutils.parsers.rst import directives, roles, states
+from docutils.transforms import misc
+
+try:
+    import urllib2
+except ImportError:
+    urllib2 = None
+
+
+def include(name, arguments, options, content, lineno,
+            content_offset, block_text, state, state_machine):
+    """Include a reST file as part of the content of this reST file."""
+    source = state_machine.input_lines.source(
+        lineno - state_machine.input_offset - 1)
+    source_dir = os.path.dirname(os.path.abspath(source))
+    path = ''.join(arguments[0].splitlines())
+    if path.find(' ') != -1:
+        error = state_machine.reporter.error(
+              '"%s" directive path contains whitespace.' % name,
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    path = os.path.normpath(os.path.join(source_dir, path))
+    path = utils.relative_path(None, path)
+    encoding = options.get('encoding', state.document.settings.input_encoding)
+    try:
+        state.document.settings.record_dependencies.add(path)
+        include_file = io.FileInput(
+            source_path=path, encoding=encoding,
+            error_handler=state.document.settings.input_encoding_error_handler,
+            handle_io_errors=None)
+    except IOError, error:
+        severe = state_machine.reporter.severe(
+              'Problems with "%s" directive path:\n%s: %s.'
+              % (name, error.__class__.__name__, error),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    include_text = include_file.read()
+    if options.has_key('literal'):
+        literal_block = nodes.literal_block(include_text, include_text,
+                                            source=path)
+        literal_block.line = 1
+        return literal_block
+    else:
+        include_lines = statemachine.string2lines(include_text,
+                                                  convert_whitespace=1)
+        state_machine.insert_input(include_lines, path)
+        return []
+
+include.arguments = (1, 0, 1)
+include.options = {'literal': directives.flag,
+                   'encoding': directives.encoding}
+
+def raw(name, arguments, options, content, lineno,
+        content_offset, block_text, state, state_machine):
+    """
+    Pass through content unchanged
+
+    Content is included in output based on type argument
+
+    Content may be included inline (content section of directive) or
+    imported from a file or url.
+    """
+    attributes = {'format': ' '.join(arguments[0].lower().split())}
+    encoding = options.get('encoding', state.document.settings.input_encoding)
+    if content:
+        if options.has_key('file') or options.has_key('url'):
+            error = state_machine.reporter.error(
+                  '"%s" directive may not both specify an external file and '
+                  'have content.' % name,
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return [error]
+        text = '\n'.join(content)
+    elif options.has_key('file'):
+        if options.has_key('url'):
+            error = state_machine.reporter.error(
+                  'The "file" and "url" options may not be simultaneously '
+                  'specified for the "%s" directive.' % name,
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return [error]
+        source_dir = os.path.dirname(
+            os.path.abspath(state.document.current_source))
+        path = os.path.normpath(os.path.join(source_dir, options['file']))
+        path = utils.relative_path(None, path)
+        try:
+            state.document.settings.record_dependencies.add(path)
+            raw_file = io.FileInput(
+                source_path=path, encoding=encoding,
+                error_handler=state.document.settings.input_encoding_error_handler,
+                handle_io_errors=None)
+        except IOError, error:
+            severe = state_machine.reporter.severe(
+                  'Problems with "%s" directive path:\n%s.' % (name, error),
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return [severe]
+        text = raw_file.read()
+        attributes['source'] = path
+    elif options.has_key('url'):
+        if not urllib2:
+            severe = state_machine.reporter.severe(
+                  'Problems with the "%s" directive and its "url" option: '
+                  'unable to access the required functionality (from the '
+                  '"urllib2" module).' % name,
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return [severe]
+        source = options['url']
+        try:
+            raw_text = urllib2.urlopen(source).read()
+        except (urllib2.URLError, IOError, OSError), error:
+            severe = state_machine.reporter.severe(
+                  'Problems with "%s" directive URL "%s":\n%s.'
+                  % (name, options['url'], error),
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return [severe]
+        raw_file = io.StringInput(
+            source=raw_text, source_path=source, encoding=encoding,
+            error_handler=state.document.settings.input_encoding_error_handler)
+        text = raw_file.read()
+        attributes['source'] = source
+    else:
+        error = state_machine.reporter.warning(
+            'The "%s" directive requires content; none supplied.' % (name),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    raw_node = nodes.raw('', text, **attributes)
+    return [raw_node]
+
+raw.arguments = (1, 0, 1)
+raw.options = {'file': directives.path,
+               'url': directives.path,
+               'encoding': directives.encoding}
+raw.content = 1
+
+def replace(name, arguments, options, content, lineno,
+            content_offset, block_text, state, state_machine):
+    if not isinstance(state, states.SubstitutionDef):
+        error = state_machine.reporter.error(
+            'Invalid context: the "%s" directive can only be used within a '
+            'substitution definition.' % (name),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    text = '\n'.join(content)
+    element = nodes.Element(text)
+    if text:
+        state.nested_parse(content, content_offset, element)
+        if len(element) != 1 or not isinstance(element[0], nodes.paragraph):
+            messages = []
+            for node in element:
+                if isinstance(node, nodes.system_message):
+                    if node.has_key('backrefs'):
+                        del node['backrefs']
+                    messages.append(node)
+            error = state_machine.reporter.error(
+                'Error in "%s" directive: may contain a single paragraph '
+                'only.' % (name), line=lineno)
+            messages.append(error)
+            return messages
+        else:
+            return element[0].children
+    else:
+        error = state_machine.reporter.error(
+            'The "%s" directive is empty; content required.' % (name),
+            line=lineno)
+        return [error]
+
+replace.content = 1
+
+def unicode_directive(name, arguments, options, content, lineno,
+                      content_offset, block_text, state, state_machine):
+    r"""
+    Convert Unicode character codes (numbers) to characters.  Codes may be
+    decimal numbers, hexadecimal numbers (prefixed by ``0x``, ``x``, ``\x``,
+    ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style numeric character
+    entities (e.g. ``&#x262E;``).  Text following ".." is a comment and is
+    ignored.  Spaces are ignored, and any other text remains as-is.
+    """
+    if not isinstance(state, states.SubstitutionDef):
+        error = state_machine.reporter.error(
+            'Invalid context: the "%s" directive can only be used within a '
+            'substitution definition.' % (name),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    substitution_definition = state_machine.node
+    if options.has_key('trim'):
+        substitution_definition.attributes['ltrim'] = 1
+        substitution_definition.attributes['rtrim'] = 1
+    if options.has_key('ltrim'):
+        substitution_definition.attributes['ltrim'] = 1
+    if options.has_key('rtrim'):
+        substitution_definition.attributes['rtrim'] = 1
+    codes = unicode_comment_pattern.split(arguments[0])[0].split()
+    element = nodes.Element()
+    for code in codes:
+        try:
+            decoded = directives.unicode_code(code)
+        except ValueError, err:
+            error = state_machine.reporter.error(
+                'Invalid character code: %s\n%s: %s'
+                % (code, err.__class__.__name__, err),
+                nodes.literal_block(block_text, block_text), line=lineno)
+            return [error]
+        element += nodes.Text(decoded)
+    return element.children
+
+unicode_directive.arguments = (1, 0, 1)
+unicode_directive.options = {'trim': directives.flag,
+                             'ltrim': directives.flag,
+                             'rtrim': directives.flag}
+unicode_comment_pattern = re.compile(r'( |\n|^)\.\. ')
+
+def class_directive(name, arguments, options, content, lineno,
+                       content_offset, block_text, state, state_machine):
+    """
+    Set a "class" attribute on the next element.
+    A "pending" element is inserted, and a transform does the work later.
+    """
+    try:
+        class_value = directives.class_option(arguments[0])
+    except ValueError:
+        error = state_machine.reporter.error(
+            'Invalid class attribute value for "%s" directive: "%s".'
+            % (name, arguments[0]),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    pending = nodes.pending(misc.ClassAttribute,
+                            {'class': class_value, 'directive': name},
+                            block_text)
+    state_machine.document.note_pending(pending)
+    return [pending]
+
+class_directive.arguments = (1, 0, 1)
+class_directive.content = 1
+
+role_arg_pat = re.compile(r'(%s)\s*(\(\s*(%s)\s*\)\s*)?$'
+                          % ((states.Inliner.simplename,) * 2))
+def role(name, arguments, options, content, lineno,
+         content_offset, block_text, state, state_machine):
+    """Dynamically create and register a custom interpreted text role."""
+    if content_offset > lineno or not content:
+        error = state_machine.reporter.error(
+            '"%s" directive requires arguments on the first line.'
+            % name, nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    args = content[0]
+    match = role_arg_pat.match(args)
+    if not match:
+        error = state_machine.reporter.error(
+            '"%s" directive arguments not valid role names: "%s".'
+            % (name, args), nodes.literal_block(block_text, block_text),
+            line=lineno)
+        return [error]
+    new_role_name = match.group(1)
+    base_role_name = match.group(3)
+    messages = []
+    if base_role_name:
+        base_role, messages = roles.role(
+            base_role_name, state_machine.language, lineno, state.reporter)
+        if base_role is None:
+            error = state.reporter.error(
+                'Unknown interpreted text role "%s".' % base_role_name,
+                nodes.literal_block(block_text, block_text), line=lineno)
+            return messages + [error]
+    else:
+        base_role = roles.generic_custom_role
+    assert not hasattr(base_role, 'arguments'), (
+        'Supplemental directive arguments for "%s" directive not supported'
+        '(specified by "%r" role).' % (name, base_role))
+    try:
+        (arguments, options, content, content_offset) = (
+            state.parse_directive_block(content[1:], content_offset, base_role,
+                                        option_presets={}))
+    except states.MarkupError, detail:
+        error = state_machine.reporter.error(
+            'Error in "%s" directive:\n%s.' % (name, detail),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return messages + [error]
+    if not options.has_key('class'):
+        try:
+            options['class'] = directives.class_option(new_role_name)
+        except ValueError, detail:
+            error = state_machine.reporter.error(
+                'Invalid argument for "%s" directive:\n%s.'
+                % (name, detail),
+                nodes.literal_block(block_text, block_text), line=lineno)
+            return messages + [error]
+    role = roles.CustomRole(new_role_name, base_role, options, content)
+    roles.register_local_role(new_role_name, role)
+    return messages
+
+role.content = 1
+
+def directive_test_function(name, arguments, options, content, lineno,
+                            content_offset, block_text, state, state_machine):
+    """This directive is useful only for testing purposes."""
+    if content:
+        text = '\n'.join(content)
+        info = state_machine.reporter.info(
+            'Directive processed. Type="%s", arguments=%r, options=%r, '
+            'content:' % (name, arguments, options),
+            nodes.literal_block(text, text), line=lineno)
+    else:
+        info = state_machine.reporter.info(
+            'Directive processed. Type="%s", arguments=%r, options=%r, '
+            'content: None' % (name, arguments, options), line=lineno)
+    return [info]
+
+directive_test_function.arguments = (0, 1, 1)
+directive_test_function.options = {'option': directives.unchanged_required}
+directive_test_function.content = 1

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/parts.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/parts.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/parts.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,84 @@
+# Author: David Goodger, Dmitry Jemerov
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for document parts.
+"""
+
+__docformat__ = 'reStructuredText'
+
+from docutils import nodes, languages
+from docutils.transforms import parts
+from docutils.parsers.rst import directives
+
+
+backlinks_values = ('top', 'entry', 'none')
+
+def backlinks(arg):
+    value = directives.choice(arg, backlinks_values)
+    if value == 'none':
+        return None
+    else:
+        return value
+
+def contents(name, arguments, options, content, lineno,
+             content_offset, block_text, state, state_machine):
+    """Table of contents."""
+    document = state_machine.document
+    language = languages.get_language(document.settings.language_code)
+
+    if arguments:
+        title_text = arguments[0]
+        text_nodes, messages = state.inline_text(title_text, lineno)
+        title = nodes.title(title_text, '', *text_nodes)
+    else:
+        messages = []
+        if options.has_key('local'):
+            title = None
+        else:
+            title = nodes.title('', language.labels['contents'])
+
+    topic = nodes.topic(CLASS='contents')
+
+    cls = options.get('class')
+    if cls:
+        topic.set_class(cls)
+
+    if title:
+        name = title.astext()
+        topic += title
+    else:
+        name = language.labels['contents']
+
+    name = nodes.fully_normalize_name(name)
+    if not document.has_name(name):
+        topic['name'] = name
+    document.note_implicit_target(topic)
+
+    pending = nodes.pending(parts.Contents, rawsource=block_text)
+    pending.details.update(options)
+    document.note_pending(pending)
+    topic += pending
+    return [topic] + messages
+
+contents.arguments = (0, 1, 1)
+contents.options = {'depth': directives.nonnegative_int,
+                    'local': directives.flag,
+                    'backlinks': backlinks,
+                    'class': directives.class_option}
+
+def sectnum(name, arguments, options, content, lineno,
+            content_offset, block_text, state, state_machine):
+    """Automatic section numbering."""
+    pending = nodes.pending(parts.SectNum)
+    pending.details.update(options)
+    state_machine.document.note_pending(pending)
+    return [pending]
+
+sectnum.options = {'depth': int,
+                   'start': int,
+                   'prefix': directives.unchanged_required,
+                   'suffix': directives.unchanged_required}

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/references.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/references.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/references.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,23 @@
+# Author: David Goodger, Dmitry Jemerov
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for references and targets.
+"""
+
+__docformat__ = 'reStructuredText'
+
+from docutils import nodes
+from docutils.transforms import references
+
+
+def target_notes(name, arguments, options, content, lineno,
+                 content_offset, block_text, state, state_machine):
+    """Target footnote generation."""
+    pending = nodes.pending(references.TargetNotes)
+    state_machine.document.note_pending(pending)
+    nodelist = [pending]
+    return nodelist

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/tables.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/tables.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/directives/tables.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,306 @@
+# Authors: David Goodger, David Priest
+# Contact: goodger at python.org
+# Revision: $Revision: 1.1.2.3 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Directives for table elements.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+import os.path
+from docutils import io, nodes, statemachine, utils
+from docutils.utils import SystemMessagePropagation
+from docutils.parsers.rst import directives
+
+try:
+    import csv                          # new in Python 2.3
+except ImportError:
+    csv = None
+
+try:
+    import urllib2
+except ImportError:
+    urllib2 = None
+
+try:
+    True
+except NameError:                       # Python 2.2 & 2.1 compatibility
+    True = not 0
+    False = not 1
+
+
+def table(name, arguments, options, content, lineno,
+          content_offset, block_text, state, state_machine):
+    if not content:
+        warning = state_machine.reporter.warning(
+            'Content block expected for the "%s" directive; none found.'
+            % name, nodes.literal_block(block_text, block_text),
+            line=lineno)
+        return [warning]
+    title, messages = make_title(arguments, state, lineno)
+    node = nodes.Element()          # anonymous container for parsing
+    text = '\n'.join(content)
+    state.nested_parse(content, content_offset, node)
+    if len(node) != 1 or not isinstance(node[0], nodes.table):
+        error = state_machine.reporter.error(
+            'Error parsing content block for the "%s" directive: '
+            'exactly one table expected.'
+            % name, nodes.literal_block(block_text, block_text),
+            line=lineno)
+        return [error]
+    table_node = node[0]
+    if options.has_key('class'):
+        table_node.set_class(options['class'])
+    if title:
+        table_node.insert(0, title)
+    return [table_node] + messages
+
+table.arguments = (0, 1, 1)
+table.options = {'class': directives.class_option}
+table.content = 1
+
+def make_title(arguments, state, lineno):
+    if arguments:
+        title_text = arguments[0]
+        text_nodes, messages = state.inline_text(title_text, lineno)
+        title = nodes.title(title_text, '', *text_nodes)
+    else:
+        title = None
+        messages = []
+    return title, messages
+
+
+if csv:
+    class DocutilsDialect(csv.Dialect):
+
+        """CSV dialect for `csv_table` directive function."""
+
+        delimiter = ','
+        quotechar = '"'
+        doublequote = True
+        skipinitialspace = True
+        lineterminator = '\n'
+        quoting = csv.QUOTE_MINIMAL
+
+        def __init__(self, options):
+            if options.has_key('delim'):
+                self.delimiter = str(options['delim'])
+            if options.has_key('keepspace'):
+                self.skipinitialspace = False
+            if options.has_key('quote'):
+                self.quotechar = str(options['quote'])
+            if options.has_key('escape'):
+                self.doublequote = False
+                self.escapechar = str(options['escape'])
+            csv.Dialect.__init__(self)
+
+
+    class HeaderDialect(csv.Dialect):
+
+        """CSV dialect to use for the "header" option data."""
+
+        delimiter = ','
+        quotechar = '"'
+        escapechar = '\\'
+        doublequote = False
+        skipinitialspace = True
+        lineterminator = '\n'
+        quoting = csv.QUOTE_MINIMAL
+
+
+def csv_table(name, arguments, options, content, lineno,
+             content_offset, block_text, state, state_machine):
+    try:
+        check_requirements(name, lineno, block_text, state_machine)
+        title, messages = make_title(arguments, state, lineno)
+        csv_data, source = get_csv_data(
+            name, options, content, lineno, block_text, state, state_machine)
+        table_head, max_header_cols = process_header_option(
+            options, state_machine, lineno)
+        rows, max_cols = parse_csv_data_into_rows(
+            csv_data, DocutilsDialect(options), source, options)
+        max_cols = max(max_cols, max_header_cols)
+        header_rows = options.get('header-rows', 0) # default 0
+        check_table_dimensions(
+            rows, header_rows, name, lineno, block_text, state_machine)
+        table_head.extend(rows[:header_rows])
+        table_body = rows[header_rows:]
+        col_widths = get_column_widths(
+            max_cols, name, options, lineno, block_text, state_machine)
+        extend_short_rows_with_empty_cells(max_cols, (table_head, table_body))
+    except SystemMessagePropagation, detail:
+        return [detail.args[0]]
+    except csv.Error, detail:
+        error = state_machine.reporter.error(
+            'Error with CSV data in "%s" directive:\n%s' % (name, detail),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    table = (col_widths, table_head, table_body)
+    table_node = state.build_table(table, content_offset)
+    if options.has_key('class'):
+        table_node.set_class(options['class'])
+    if title:
+        table_node.insert(0, title)
+    return [table_node] + messages
+
+csv_table.arguments = (0, 1, 1)
+csv_table.options = {'header-rows': directives.nonnegative_int,
+                     'header': directives.unchanged,
+                     'widths': directives.positive_int_list,
+                     'file': directives.path,
+                     'url': directives.path,
+                     'encoding': directives.encoding,
+                     'class': directives.class_option,
+                     # field delimiter char
+                     'delim': directives.single_char_or_whitespace_or_unicode,
+                     # treat whitespace after delimiter as significant
+                     'keepspace': directives.flag,
+                     # text field quote/unquote char:
+                     'quote': directives.single_char_or_unicode,
+                     # char used to escape delim & quote as-needed:
+                     'escape': directives.single_char_or_unicode,}
+csv_table.content = 1
+
+def check_requirements(name, lineno, block_text, state_machine):
+    if not csv:
+        error = state_machine.reporter.error(
+            'The "%s" directive is not compatible with this version of '
+            'Python (%s).  Requires the "csv" module, new in Python 2.3.'
+            % (name, sys.version.split()[0]),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        raise SystemMessagePropagation(error)
+
+def get_csv_data(name, options, content, lineno, block_text,
+                 state, state_machine):
+    """
+    CSV data can come from the directive content, from an external file, or
+    from a URL reference.
+    """
+    encoding = options.get('encoding', state.document.settings.input_encoding)
+    if content:                         # CSV data is from directive content
+        if options.has_key('file') or options.has_key('url'):
+            error = state_machine.reporter.error(
+                  '"%s" directive may not both specify an external file and '
+                  'have content.' % name,
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(error)
+        source = content.source(0)
+        csv_data = content
+    elif options.has_key('file'):       # CSV data is from an external file
+        if options.has_key('url'):
+            error = state_machine.reporter.error(
+                  'The "file" and "url" options may not be simultaneously '
+                  'specified for the "%s" directive.' % name,
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(error)
+        source_dir = os.path.dirname(
+            os.path.abspath(state.document.current_source))
+        source = os.path.normpath(os.path.join(source_dir, options['file']))
+        source = utils.relative_path(None, source)
+        try:
+            state.document.settings.record_dependencies.add(source)
+            csv_file = io.FileInput(
+                source_path=source, encoding=encoding,
+                error_handler=state.document.settings.input_encoding_error_handler,
+                handle_io_errors=None)
+            csv_data = csv_file.read().splitlines()
+        except IOError, error:
+            severe = state_machine.reporter.severe(
+                  'Problems with "%s" directive path:\n%s.' % (name, error),
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(severe)
+    elif options.has_key('url'):        # CSV data is from a URL
+        if not urllib2:
+            severe = state_machine.reporter.severe(
+                  'Problems with the "%s" directive and its "url" option: '
+                  'unable to access the required functionality (from the '
+                  '"urllib2" module).' % name,
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(severe)
+        source = options['url']
+        try:
+            csv_text = urllib2.urlopen(source).read()
+        except (urllib2.URLError, IOError, OSError, ValueError), error:
+            severe = state_machine.reporter.severe(
+                  'Problems with "%s" directive URL "%s":\n%s.'
+                  % (name, options['url'], error),
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(severe)
+        csv_file = io.StringInput(
+            source=csv_text, source_path=source, encoding=encoding,
+            error_handler=state.document.settings.input_encoding_error_handler)
+        csv_data = csv_file.read().splitlines()
+    else:
+        error = state_machine.reporter.warning(
+            'The "%s" directive requires content; none supplied.' % (name),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        raise SystemMessagePropagation(error)
+    return csv_data, source
+
+def process_header_option(options, state_machine, lineno):
+    source = state_machine.get_source(lineno - 1)
+    table_head = []
+    max_header_cols = 0
+    if options.has_key('header'):       # separate table header in option
+        rows, max_header_cols = parse_csv_data_into_rows(
+            options['header'].split('\n'), HeaderDialect(), source, options)
+        table_head.extend(rows)
+    return table_head, max_header_cols
+
+def parse_csv_data_into_rows(csv_data, dialect, source, options):
+    # csv.py doesn't do Unicode; encode temporarily as UTF-8
+    csv_reader = csv.reader([line.encode('utf-8') for line in csv_data],
+                            dialect=dialect)
+    rows = []
+    max_cols = 0
+    for row in csv_reader:
+        row_data = []
+        for cell in row:
+            # decode UTF-8 back to Unicode
+            cell_text = unicode(cell, 'utf-8')
+            cell_data = (0, 0, 0, statemachine.StringList(
+                cell_text.splitlines(), source=source))
+            row_data.append(cell_data)
+        rows.append(row_data)
+        max_cols = max(max_cols, len(row))
+    return rows, max_cols
+
+def check_table_dimensions(rows, header_rows, name, lineno, block_text,
+                           state_machine):
+    if len(rows) < header_rows:
+        error = state_machine.reporter.error(
+            '%s header row(s) specified but only %s row(s) of data supplied '
+            '("%s" directive).' % (header_rows, len(rows), name),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        raise SystemMessagePropagation(error)
+    elif len(rows) == header_rows > 0:
+        error = state_machine.reporter.error(
+            'Insufficient data supplied (%s row(s)); no data remaining for '
+            'table body, required by "%s" directive.' % (len(rows), name),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        raise SystemMessagePropagation(error)
+
+def get_column_widths(max_cols, name, options, lineno, block_text,
+                      state_machine):
+    if options.has_key('widths'):
+        col_widths = options['widths']
+        if len(col_widths) != max_cols:
+            error = state_machine.reporter.error(
+              '"%s" widths do not match the number of columns in table (%s).'
+              % (name, max_cols),
+              nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(error)
+    else:
+        col_widths = [100 / max_cols] * max_cols
+    return col_widths
+
+def extend_short_rows_with_empty_cells(columns, parts):
+    for part in parts:
+        for row in part:
+            if len(row) < columns:
+                row.extend([(0, 0, 0, [])] * (columns - len(row)))

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,27 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# Internationalization details are documented in
+# <http://docutils.sf.net/docs/howto/i18n.html>.
+
+"""
+This package contains modules for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+_languages = {}
+
+def get_language(language_code):
+    if _languages.has_key(language_code):
+        return _languages[language_code]
+    try:
+        module = __import__(language_code, globals(), locals())
+    except ImportError:
+        return None
+    _languages[language_code] = module
+    return module

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/af.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/af.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/af.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,97 @@
+# Author: Jannie Hofmeyr
+# Contact: jhsh at sun.ac.za
+# Revision: $Revision: 1.1.2.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Afrikaans-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      'aandag': 'attention',
+      'versigtig': 'caution',
+      'gevaar': 'danger',
+      'fout': 'error',
+      'wenk': 'hint',
+      'belangrik': 'important',
+      'nota': 'note',
+      'tip': 'tip', # hint and tip both have the same translation: wenk
+      'waarskuwing': 'warning',
+      'vermaning': 'admonition',
+      'kantstreep': 'sidebar',
+      'onderwerp': 'topic',
+      'lynblok': 'line-block',
+      'parsed-literal (translation required)': 'parsed-literal',
+      'rubriek': 'rubric',
+      'epigraaf': 'epigraph',
+      'hoogtepunte': 'highlights',
+      'pull-quote (translation required)': 'pull-quote',
+      u'compound (translation required)': 'compound',
+      #'vrae': 'questions',
+      #'qa': 'questions',
+      #'faq': 'questions',
+      'table (translation required)': 'table',
+      'csv-table (translation required)': 'csv-table',
+      'meta': 'meta',
+      #'beeldkaart': 'imagemap',
+      'beeld': 'image',
+      'figuur': 'figure',
+      'insluiting': 'include',
+      'rou': 'raw',
+      'vervang': 'replace',
+      'unicode': 'unicode', # should this be translated? unikode
+      'klas': 'class',
+      'role (translation required)': 'role',
+      'inhoud': 'contents',
+      'sectnum': 'sectnum',
+      'section-numbering': 'sectnum',
+      #'voetnote': 'footnotes',
+      #'aanhalings': 'citations',
+      'teikennotas': 'target-notes',
+      'restructuredtext-test-directive': 'restructuredtext-test-directive'}
+"""Afrikaans name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+    'afkorting': 'abbreviation',
+    'ab': 'abbreviation',
+    'akroniem': 'acronym',
+    'ac': 'acronym',
+    'indeks': 'index',
+    'i': 'index',
+    'voetskrif': 'subscript',
+    'sub': 'subscript',
+    'boskrif': 'superscript',
+    'sup': 'superscript',
+    'titelverwysing': 'title-reference',
+    'titel': 'title-reference',
+    't': 'title-reference',
+    'pep-verwysing': 'pep-reference',
+    'pep': 'pep-reference',
+    'rfc-verwysing': 'rfc-reference',
+    'rfc': 'rfc-reference',
+    'nadruk': 'emphasis',
+    'sterk': 'strong',
+    'literal (translation required)': 'literal',
+    'benoemde verwysing': 'named-reference',
+    'anonieme verwysing': 'anonymous-reference',
+    'voetnootverwysing': 'footnote-reference',
+    'aanhalingverwysing': 'citation-reference',
+    'vervangingsverwysing': 'substitution-reference',
+    'teiken': 'target',
+    'uri-verwysing': 'uri-reference',
+    'uri': 'uri-reference',
+    'url': 'uri-reference',
+    'rou': 'raw',}
+"""Mapping of Afrikaans role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/cs.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/cs.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/cs.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,99 @@
+# Author: Marek Blaha
+# Contact: mb at dat.cz
+# Revision: $Revision: 1.1.4.4 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Czech-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      # language-dependent: fixed
+      u'pozor': 'attention',
+      u'caution (translation required)': 'caution', # jak rozlisit caution a warning?
+      u'nebezpe\u010D\u00ED': 'danger',
+      u'chyba': 'error',
+      u'rada': 'hint',
+      u'd\u016Fle\u017Eit\u00E9': 'important',
+      u'pozn\u00E1mka': 'note',
+      u'tip (translation required)': 'tip',
+      u'varov\u00E1n\u00ED': 'warning',
+      u'admonition (translation required)': 'admonition',
+      u'sidebar (translation required)': 'sidebar',
+      u't\u00E9ma': 'topic',
+      u'line-block (translation required)': 'line-block',
+      u'parsed-literal (translation required)': 'parsed-literal',
+      u'odd\u00EDl': 'rubric',
+      u'moto': 'epigraph',
+      u'highlights (translation required)': 'highlights',
+      u'pull-quote (translation required)': 'pull-quote',
+      u'compound (translation required)': 'compound',
+      #'questions': 'questions',
+      #'qa': 'questions',
+      #'faq': 'questions',
+      u'table (translation required)': 'table',
+      u'csv-table (translation required)': 'csv-table',
+      u'meta (translation required)': 'meta',
+      #'imagemap': 'imagemap',
+      u'image (translation required)': 'image',   # obrazek
+      u'figure (translation required)': 'figure', # a tady?
+      u'include (translation required)': 'include',
+      u'raw (translation required)': 'raw',
+      u'replace (translation required)': 'replace',
+      u'unicode (translation required)': 'unicode',
+      u't\u0159\u00EDda': 'class',
+      u'role (translation required)': 'role',
+      u'obsah': 'contents',
+      u'sectnum (translation required)': 'sectnum',
+      u'section-numbering (translation required)': 'sectnum',
+      #'footnotes': 'footnotes',
+      #'citations': 'citations',
+      u'target-notes (translation required)': 'target-notes',
+      u'restructuredtext-test-directive': 'restructuredtext-test-directive'}
+"""Czech name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+    # language-dependent: fixed
+    u'abbreviation (translation required)': 'abbreviation',
+    u'ab (translation required)': 'abbreviation',
+    u'acronym (translation required)': 'acronym',
+    u'ac (translation required)': 'acronym',
+    u'index (translation required)': 'index',
+    u'i (translation required)': 'index',
+    u'subscript (translation required)': 'subscript',
+    u'sub (translation required)': 'subscript',
+    u'superscript (translation required)': 'superscript',
+    u'sup (translation required)': 'superscript',
+    u'title-reference (translation required)': 'title-reference',
+    u'title (translation required)': 'title-reference',
+    u't (translation required)': 'title-reference',
+    u'pep-reference (translation required)': 'pep-reference',
+    u'pep (translation required)': 'pep-reference',
+    u'rfc-reference (translation required)': 'rfc-reference',
+    u'rfc (translation required)': 'rfc-reference',
+    u'emphasis (translation required)': 'emphasis',
+    u'strong (translation required)': 'strong',
+    u'literal (translation required)': 'literal',
+    u'named-reference (translation required)': 'named-reference',
+    u'anonymous-reference (translation required)': 'anonymous-reference',
+    u'footnote-reference (translation required)': 'footnote-reference',
+    u'citation-reference (translation required)': 'citation-reference',
+    u'substitution-reference (translation required)': 'substitution-reference',
+    u'target (translation required)': 'target',
+    u'uri-reference (translation required)': 'uri-reference',
+    u'uri (translation required)': 'uri-reference',
+    u'url (translation required)': 'uri-reference',
+    u'raw (translation required)': 'raw',}
+"""Mapping of Czech role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/de.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/de.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/de.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,90 @@
+# Authors: Engelbert Gruber; Felix Wiemann
+# Contact: grubert at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+German-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      'achtung': 'attention',
+      'vorsicht': 'caution',
+      'gefahr': 'danger',
+      'fehler': 'error',
+      'hinweis': 'hint',
+      'wichtig': 'important',
+      'notiz': 'note',
+      'tipp': 'tip',
+      'warnung': 'warning',
+      'ermahnung': 'admonition',
+      'kasten': 'sidebar',
+      'seitenkasten': 'sidebar',
+      'thema': 'topic',
+      'zeilen-block': 'line-block',
+      'parsed-literal (translation required)': 'parsed-literal',
+      'rubrik': 'rubric',
+      'epigraph': 'epigraph',
+      'highlights (translation required)': 'highlights',
+      'pull-quote (translation required)': 'pull-quote', # kasten too ?
+      'zusammengesetzt': 'compound',
+      'verbund': 'compound',
+      #'fragen': 'questions',
+      'tabelle': 'table',
+      'csv-tabelle': 'csv-table',
+      'meta': 'meta',
+      #'imagemap': 'imagemap',
+      'bild': 'image',
+      'abbildung': 'figure',
+      u'unver\xe4ndert': 'raw',
+      u'roh': 'raw',
+      u'einf\xfcgen': 'include',
+      'ersetzung': 'replace',
+      'ersetzen': 'replace',
+      'ersetze': 'replace',
+      'unicode': 'unicode',
+      'klasse': 'class',
+      'rolle': 'role',
+      'inhalt': 'contents',
+      'kapitel-nummerierung': 'sectnum',
+      'abschnitts-nummerierung': 'sectnum',
+      u'linkziel-fu\xdfnoten': 'target-notes',
+      #u'fu\xdfnoten': 'footnotes',
+      #'zitate': 'citations',
+      }
+"""German name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+      u'abk\xfcrzung': 'abbreviation',
+      'akronym': 'acronym',
+      'index': 'index',
+      'tiefgestellt': 'subscript',
+      'hochgestellt': 'superscript',
+      'titel-referenz': 'title-reference',
+      'pep-referenz': 'pep-reference',
+      'rfc-referenz': 'rfc-reference',
+      'betonung': 'emphasis',
+      'fett': 'strong',
+      u'w\xf6rtlich': 'literal',
+      'benannte-referenz': 'named-reference',
+      'unbenannte-referenz': 'anonymous-reference',
+      u'fu\xdfnoten-referenz': 'footnote-reference',
+      'zitat-referenz': 'citation-reference',
+      'ersetzungs-referenz': 'substitution-reference',
+      'ziel': 'target',
+      'uri-referenz': 'uri-reference',
+      u'unver\xe4ndert': 'raw',
+      u'roh': 'raw',}
+"""Mapping of German role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/en.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/en.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/en.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,99 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+English-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      # language-dependent: fixed
+      'attention': 'attention',
+      'caution': 'caution',
+      'danger': 'danger',
+      'error': 'error',
+      'hint': 'hint',
+      'important': 'important',
+      'note': 'note',
+      'tip': 'tip',
+      'warning': 'warning',
+      'admonition': 'admonition',
+      'sidebar': 'sidebar',
+      'topic': 'topic',
+      'line-block': 'line-block',
+      'parsed-literal': 'parsed-literal',
+      'rubric': 'rubric',
+      'epigraph': 'epigraph',
+      'highlights': 'highlights',
+      'pull-quote': 'pull-quote',
+      'compound': 'compound',
+      #'questions': 'questions',
+      'table': 'table',
+      'csv-table': 'csv-table',
+      #'qa': 'questions',
+      #'faq': 'questions',
+      'meta': 'meta',
+      #'imagemap': 'imagemap',
+      'image': 'image',
+      'figure': 'figure',
+      'include': 'include',
+      'raw': 'raw',
+      'replace': 'replace',
+      'unicode': 'unicode',
+      'class': 'class',
+      'role': 'role',
+      'contents': 'contents',
+      'sectnum': 'sectnum',
+      'section-numbering': 'sectnum',
+      #'footnotes': 'footnotes',
+      #'citations': 'citations',
+      'target-notes': 'target-notes',
+      'restructuredtext-test-directive': 'restructuredtext-test-directive'}
+"""English name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+    # language-dependent: fixed
+    'abbreviation': 'abbreviation',
+    'ab': 'abbreviation',
+    'acronym': 'acronym',
+    'ac': 'acronym',
+    'index': 'index',
+    'i': 'index',
+    'subscript': 'subscript',
+    'sub': 'subscript',
+    'superscript': 'superscript',
+    'sup': 'superscript',
+    'title-reference': 'title-reference',
+    'title': 'title-reference',
+    't': 'title-reference',
+    'pep-reference': 'pep-reference',
+    'pep': 'pep-reference',
+    'rfc-reference': 'rfc-reference',
+    'rfc': 'rfc-reference',
+    'emphasis': 'emphasis',
+    'strong': 'strong',
+    'literal': 'literal',
+    'named-reference': 'named-reference',
+    'anonymous-reference': 'anonymous-reference',
+    'footnote-reference': 'footnote-reference',
+    'citation-reference': 'citation-reference',
+    'substitution-reference': 'substitution-reference',
+    'target': 'target',
+    'uri-reference': 'uri-reference',
+    'uri': 'uri-reference',
+    'url': 'uri-reference',
+    'raw': 'raw',}
+"""Mapping of English role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/eo.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/eo.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/eo.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,108 @@
+# Author: Marcelo Huerta San Martin
+# Contact: richieadler at users.sourceforge.net
+# Revision: $Revision: 1.1.2.5 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Esperanto-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      # language-dependent: fixed
+      u'atentu': 'attention',
+      u'zorgu': 'caution',
+      u'dangxero': 'danger',
+      u'dan\u011dero': 'danger',
+      u'eraro': 'error',
+      u'spuro': 'hint',
+      u'grava': 'important',
+      u'noto': 'note',
+      u'helpeto': 'tip',
+      u'averto': 'warning',
+      u'admono': 'admonition',
+      u'flankteksto': 'sidebar',
+      u'temo': 'topic',
+      u'linea-bloko': 'line-block',
+      u'analizota-literalo': 'parsed-literal',
+      u'rubriko': 'rubric',
+      u'epigrafo': 'epigraph',
+      u'elstarajxoj': 'highlights',
+      u'elstara\u0135oj': 'highlights',
+      u'ekstera-citajxo': 'pull-quote',
+      u'ekstera-cita\u0135o': 'pull-quote',
+      u'kombinajxo': 'compound',
+      u'kombina\u0135o': 'compound',
+      #'questions': 'questions',
+      #'qa': 'questions',
+      #'faq': 'questions',
+      u'tabelo': 'table',
+      u'tabelo-vdk': 'csv-table', # "valoroj disigitaj per komoj"
+      u'tabelo-csv': 'csv-table',
+      u'meta': 'meta',
+      #'imagemap': 'imagemap',
+      u'bildo': 'image',
+      u'figuro': 'figure',
+      u'inkludi': 'include',
+      u'senanaliza': 'raw',
+      u'anstatauxi': 'replace',
+      u'anstata\u016di': 'replace',
+      u'unicode': 'unicode',
+      u'klaso': 'class',
+      u'rolo': 'role',
+      u'enhavo': 'contents',
+      u'seknum': 'sectnum',
+      u'sekcia-numerado': 'sectnum',
+      #'footnotes': 'footnotes',
+      #'citations': 'citations',
+      u'celaj-notoj': 'target-notes',
+      u'restructuredtext-test-directive': 'restructuredtext-test-directive'}
+"""Esperanto name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+    # language-dependent: fixed
+    u'mallongigo': 'abbreviation',
+    u'mall': 'abbreviation',
+    u'komenclitero': 'acronym',
+    u'kl': 'acronym',
+    u'indekso': 'index',
+    u'i': 'index',
+    u'subskribo': 'subscript',
+    u'sub': 'subscript',
+    u'supraskribo': 'superscript',
+    u'sup': 'superscript',
+    u'titola-referenco': 'title-reference',
+    u'titolo': 'title-reference',
+    u't': 'title-reference',
+    u'pep-referenco': 'pep-reference',
+    u'pep': 'pep-reference',
+    u'rfc-referenco': 'rfc-reference',
+    u'rfc': 'rfc-reference',
+    u'emfazo': 'emphasis',
+    u'forta': 'strong',
+    u'litera': 'literal',
+    u'nomita-referenco': 'named-reference',
+    u'nenomita-referenco': 'anonymous-reference',
+    u'piednota-referenco': 'footnote-reference',
+    u'citajxo-referenco': 'citation-reference',
+    u'cita\u0135o-referenco': 'citation-reference',
+    u'anstatauxa-referenco': 'substitution-reference',
+    u'anstata\u016da-referenco': 'substitution-reference',
+    u'celo': 'target',
+    u'uri-referenco': 'uri-reference',
+    u'uri': 'uri-reference',
+    u'url': 'uri-reference',
+    u'senanaliza': 'raw',
+}
+"""Mapping of Esperanto role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/es.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/es.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/es.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,114 @@
+# -*- coding: iso-8859-1 -*-
+# Author: Marcelo Huerta San Martín
+# Contact: richieadler at users.sourceforge.net
+# Revision: $Revision: 1.1.2.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Spanish-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      u'atenci\u00f3n': 'attention',
+      u'atencion': 'attention',
+      u'precauci\u00f3n': 'caution',
+      u'precaucion': 'caution',
+      u'peligro': 'danger',
+      u'error': 'error',
+      u'sugerencia': 'hint',
+      u'importante': 'important',
+      u'nota': 'note',
+      u'consejo': 'tip',
+      u'advertencia': 'warning',
+      u'exhortacion': 'admonition',
+      u'exhortaci\u00f3n': 'admonition',
+      u'nota-al-margen': 'sidebar',
+      u'tema': 'topic',
+      u'bloque-de-lineas': 'line-block',
+      u'bloque-de-l\u00edneas': 'line-block',
+      u'literal-evaluado': 'parsed-literal',
+      u'firma': 'rubric',
+      u'ep\u00edgrafe': 'epigraph',
+      u'epigrafe': 'epigraph',
+      u'destacado': 'highlights',
+      u'cita-destacada': 'pull-quote',
+      u'combinacion': 'compound',
+      u'combinaci\u00f3n': 'compound',
+      #'questions': 'questions',
+      #'qa': 'questions',
+      #'faq': 'questions',
+      u'tabla': 'table',
+      u'tabla-vsc': 'csv-table',
+      u'tabla-csv': 'csv-table',
+      u'meta': 'meta',
+      #'imagemap': 'imagemap',
+      u'imagen': 'image',
+      u'figura': 'figure',
+      u'incluir': 'include',
+      u'sin-analisis': 'raw',
+      u'sin-an\u00e1lisis': 'raw',
+      u'reemplazar': 'replace',
+      u'unicode': 'unicode',
+      u'clase': 'class',
+      u'rol': 'role',
+      u'contenido': 'contents',
+      u'numseccion': 'sectnum',
+      u'numsecci\u00f3n': 'sectnum',
+      u'numeracion-seccion': 'sectnum',
+      u'numeraci\u00f3n-secci\u00f3n': 'sectnum',
+      u'notas-destino': 'target-notes',
+      #'footnotes': 'footnotes',
+      #'citations': 'citations',
+      u'restructuredtext-test-directive': 'restructuredtext-test-directive'}
+"""Spanish name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+    u'abreviatura': 'abbreviation',
+    u'ab': 'abbreviation',
+    u'acronimo': 'acronym',
+    u'acronimo': 'acronym',
+    u'ac': 'acronym',
+    u'indice': 'index',
+    u'i': 'index',
+    u'subindice': 'subscript',
+    u'sub\u00edndice': 'subscript',
+    u'superindice': 'superscript',
+    u'super\u00edndice': 'superscript',
+    u'referencia-titulo': 'title-reference',
+    u'titulo': 'title-reference',
+    u't': 'title-reference',
+    u'referencia-pep': 'pep-reference',
+    u'pep': 'pep-reference',
+    u'referencia-rfc': 'rfc-reference',
+    u'rfc': 'rfc-reference',
+    u'enfasis': 'emphasis',
+    u'\u00e9nfasis': 'emphasis',
+    u'destacado': 'strong',
+    u'literal': 'literal',              # "literal" is also a word in Spanish :-)
+    u'referencia-con-nombre': 'named-reference',
+    u'referencia-anonima': 'anonymous-reference',
+    u'referencia-an\u00f3nima': 'anonymous-reference',
+    u'referencia-nota-al-pie': 'footnote-reference',
+    u'referencia-cita': 'citation-reference',
+    u'referencia-sustitucion': 'substitution-reference',
+    u'referencia-sustituci\u00f3n': 'substitution-reference',
+    u'destino': 'target',
+    u'referencia-uri': 'uri-reference',
+    u'uri': 'uri-reference',
+    u'url': 'uri-reference',
+    u'sin-analisis': 'raw',
+    u'sin-an\u00e1lisis': 'raw',
+}
+"""Mapping of Spanish role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/fi.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/fi.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/fi.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,88 @@
+# Author: Asko Soukka
+# Contact: asko.soukka at iki.fi
+# Revision: $Revision: 1.1.2.1 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Finnish-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      # language-dependent: fixed
+      u'huomio': u'attention',
+      u'varo': u'caution',
+      u'vaara': u'danger',
+      u'virhe': u'error',
+      u'vihje': u'hint',
+      u't\u00e4rke\u00e4\u00e4': u'important',
+      u'huomautus': u'note',
+      u'neuvo': u'tip',
+      u'varoitus': u'warning',
+      u'kehotus': u'admonition',
+      u'sivupalkki': u'sidebar',
+      u'aihe': u'topic',
+      u'rivi': u'line-block',
+      u'tasalevyinen': u'parsed-literal',
+      u'ohje': u'rubric',
+      u'epigraafi': u'epigraph',
+      u'kohokohdat': u'highlights',
+      u'lainaus': u'pull-quote',
+      u'taulukko': u'table',
+      u'csv-taulukko': u'csv-table',
+      u'compound (translation required)': 'compound',
+      #u'kysymykset': u'questions',
+      u'meta': u'meta',
+      #u'kuvakartta': u'imagemap',
+      u'kuva': u'image',
+      u'kaavio': u'figure',
+      u'sis\u00e4llyt\u00e4': u'include',
+      u'raaka': u'raw',
+      u'korvaa': u'replace',
+      u'unicode': u'unicode',
+      u'luokka': u'class',
+      u'rooli': u'role',
+      u'sis\u00e4llys': u'contents',
+      u'kappale': u'sectnum',
+      #u'alaviitteet': u'footnotes',
+      #u'viitaukset': u'citations',
+      u'target-notes (translation required)': u'target-notes'}
+"""Finnish name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+    # language-dependent: fixed
+    u'lyhennys': u'abbreviation',
+    u'akronyymi': u'acronym',
+    u'kirjainsana': u'acronym',
+    u'hakemisto': u'index',
+    u'luettelo': u'index',
+    u'alaindeksi': u'subscript',
+    u'indeksi': u'subscript',
+    u'yl\u00e4indeksi': u'superscript',
+    u'title-reference (translation required)': u'title-reference',
+    u'title (translation required)': u'title-reference',
+    u'pep-reference (translation required)': u'pep-reference',
+    u'rfc-reference (translation required)': u'rfc-reference',
+    u'korostus': u'emphasis',
+    u'vahvistus': u'strong',
+    u'tasalevyinen': u'literal',
+    u'named-reference (translation required)': u'named-reference',
+    u'anonymous-reference (translation required)': u'anonymous-reference',
+    u'footnote-reference (translation required)': u'footnote-reference',
+    u'citation-reference (translation required)': u'citation-reference',
+    u'substitution-reference (translation required)': u'substitution-reference',
+    u'kohde': u'target',
+    u'uri-reference (translation required)': u'uri-reference',
+    u'raw (translation required)': 'raw',}
+"""Mapping of Finnish role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/fr.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/fr.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/fr.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,94 @@
+# Authors: David Goodger; William Dode
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+French-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      u'attention': 'attention',
+      u'pr\u00E9caution': 'caution',
+      u'danger': 'danger',
+      u'erreur': 'error',
+      u'conseil': 'hint',
+      u'important': 'important',
+      u'note': 'note',
+      u'astuce': 'tip',
+      u'avertissement': 'warning',
+      u'admonition': 'admonition',
+      u'encadr\u00E9': 'sidebar',
+      u'sujet': 'topic',
+      u'bloc-textuel': 'line-block',
+      u'bloc-interpr\u00E9t\u00E9': 'parsed-literal',
+      u'code-interpr\u00E9t\u00E9': 'parsed-literal',
+      u'intertitre': 'rubric',
+      u'exergue': 'epigraph',
+      u'\u00E9pigraphe': 'epigraph',
+      u'chapeau': 'highlights',
+      u'accroche': 'pull-quote',
+      u'compound (translation required)': 'compound',
+      #u'questions': 'questions',
+      #u'qr': 'questions',
+      #u'faq': 'questions',
+      u'tableau': 'table',
+      u'csv-table (translation required)': 'csv-table',
+      u'm\u00E9ta': 'meta',
+      #u'imagemap (translation required)': 'imagemap',
+      u'image': 'image',
+      u'figure': 'figure',
+      u'inclure': 'include',
+      u'brut': 'raw',
+      u'remplacer': 'replace',
+      u'remplace': 'replace',
+      u'unicode': 'unicode',
+      u'classe': 'class',
+      u'role (translation required)': 'role',
+      u'sommaire': 'contents',
+      u'table-des-mati\u00E8res': 'contents',
+      u'sectnum': 'sectnum',
+      u'section-num\u00E9rot\u00E9e': 'sectnum',
+      u'liens': 'target-notes',
+      #u'footnotes (translation required)': 'footnotes',
+      #u'citations (translation required)': 'citations',
+      }
+"""French name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+      u'abr\u00E9viation': 'abbreviation',
+      u'acronyme': 'acronym',
+      u'sigle': 'acronym',
+      u'index': 'index',
+      u'indice': 'subscript',
+      u'ind': 'subscript',
+      u'exposant': 'superscript',
+      u'exp': 'superscript',
+      u'titre-r\u00E9f\u00E9rence': 'title-reference',
+      u'titre': 'title-reference',
+      u'pep-r\u00E9f\u00E9rence': 'pep-reference',
+      u'rfc-r\u00E9f\u00E9rence': 'rfc-reference',
+      u'emphase': 'emphasis',
+      u'fort': 'strong',
+      u'litt\u00E9ral': 'literal',
+      u'nomm\u00E9e-r\u00E9f\u00E9rence': 'named-reference',
+      u'anonyme-r\u00E9f\u00E9rence': 'anonymous-reference',
+      u'note-r\u00E9f\u00E9rence': 'footnote-reference',
+      u'citation-r\u00E9f\u00E9rence': 'citation-reference',
+      u'substitution-r\u00E9f\u00E9rence': 'substitution-reference',
+      u'lien': 'target',
+      u'uri-r\u00E9f\u00E9rence': 'uri-reference',
+      u'brut': 'raw',}
+"""Mapping of French role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/it.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/it.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/it.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,86 @@
+# Author: Nicola Larosa, Lele Gaifax
+# Contact: docutils at tekNico.net, lele at seldati.it
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Italian-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      'attenzione': 'attention',
+      'cautela': 'caution',
+      'pericolo': 'danger',
+      'errore': 'error',
+      'suggerimento': 'hint',
+      'importante': 'important',
+      'nota': 'note',
+      'consiglio': 'tip',
+      'avvertenza': 'warning',
+      'ammonizione': 'admonition',
+      'riquadro': 'sidebar',
+      'argomento': 'topic',
+      'blocco-di-righe': 'line-block',
+      'blocco-interpretato': 'parsed-literal',
+      'rubrica': 'rubric',
+      'epigrafe': 'epigraph',
+      'evidenzia': 'highlights',
+      'pull-quote (translation required)': 'pull-quote',
+      'compound (translation required)': 'compound',
+      #'questions': 'questions',
+      #'qa': 'questions',
+      #'faq': 'questions',
+      'tabella': 'table',
+      'csv-table (translation required)': 'csv-table',
+      'meta': 'meta',
+      #'imagemap': 'imagemap',
+      'immagine': 'image',
+      'figura': 'figure',
+      'includi': 'include',
+      'grezzo': 'raw',
+      'sostituisci': 'replace',
+      'unicode': 'unicode',
+      'classe': 'class',
+      'ruolo': 'role',
+      'indice': 'contents',
+      'seznum': 'sectnum',
+      'sezioni-autonumerate': 'sectnum',
+      'annota-riferimenti-esterni': 'target-notes',
+      #'footnotes': 'footnotes',
+      #'citations': 'citations',
+      'restructuredtext-test-directive': 'restructuredtext-test-directive'}
+"""Italian name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+      'abbreviazione': 'abbreviation',
+      'acronimo': 'acronym',
+      'indice': 'index',
+      'deponente': 'subscript',
+      'esponente': 'superscript',
+      'riferimento-titolo': 'title-reference',
+      'riferimento-pep': 'pep-reference',
+      'riferimento-rfc': 'rfc-reference',
+      'enfasi': 'emphasis',
+      'forte': 'strong',
+      'letterale': 'literal',
+      'riferimento-con-nome': 'named-reference',
+      'riferimento-anonimo': 'anonymous-reference',
+      'riferimento-nota': 'footnote-reference',
+      'riferimento-citazione': 'citation-reference',
+      'riferimento-sostituzione': 'substitution-reference',
+      'destinazione': 'target',
+      'riferimento-uri': 'uri-reference',
+      'grezzo': 'raw',}
+"""Mapping of Italian role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/pt_br.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/pt_br.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/pt_br.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,99 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.1.4.4 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Brazilian Portuguese-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      # language-dependent: fixed
+      u'aten\u00E7\u00E3o': 'attention',
+      'cuidado': 'caution',
+      'perigo': 'danger',
+      'erro': 'error',
+      u'sugest\u00E3o': 'hint',
+      'importante': 'important',
+      'nota': 'note',
+      'dica': 'tip',
+      'aviso': 'warning',
+      u'exorta\u00E7\u00E3o': 'admonition',
+      'barra-lateral': 'sidebar',
+      u't\u00F3pico': 'topic',
+      'bloco-de-linhas': 'line-block',
+      'literal-interpretado': 'parsed-literal',
+      'rubrica': 'rubric',
+      u'ep\u00EDgrafo': 'epigraph',
+      'destaques': 'highlights',
+      u'cita\u00E7\u00E3o-destacada': 'pull-quote',
+      u'compound (translation required)': 'compound',
+      #'perguntas': 'questions',
+      #'qa': 'questions',
+      #'faq': 'questions',
+      u'table (translation required)': 'table',
+      u'csv-table (translation required)': 'csv-table',
+      'meta': 'meta',
+      #'imagemap': 'imagemap',
+      'imagem': 'image',
+      'figura': 'figure',
+      u'inclus\u00E3o': 'include',
+      'cru': 'raw',
+      u'substitui\u00E7\u00E3o': 'replace',
+      'unicode': 'unicode',
+      'classe': 'class',
+      'role (translation required)': 'role',
+      u'\u00EDndice': 'contents',
+      'numsec': 'sectnum',
+      u'numera\u00E7\u00E3o-de-se\u00E7\u00F5es': 'sectnum',
+      #u'notas-de-rorap\u00E9': 'footnotes',
+      #u'cita\u00E7\u00F5es': 'citations',
+      u'links-no-rodap\u00E9': 'target-notes',
+      'restructuredtext-test-directive': 'restructuredtext-test-directive'}
+"""Brazilian Portuguese name to registered (in directives/__init__.py)
+directive name mapping."""
+
+roles = {
+    # language-dependent: fixed
+    u'abbrevia\u00E7\u00E3o': 'abbreviation',
+    'ab': 'abbreviation',
+    u'acr\u00F4nimo': 'acronym',
+    'ac': 'acronym',
+    u'\u00EDndice-remissivo': 'index',
+    'i': 'index',
+    'subscrito': 'subscript',
+    'sub': 'subscript',
+    'sobrescrito': 'superscript',
+    'sob': 'superscript',
+    u'refer\u00EAncia-a-t\u00EDtulo': 'title-reference',
+    u't\u00EDtulo': 'title-reference',
+    't': 'title-reference',
+    u'refer\u00EAncia-a-pep': 'pep-reference',
+    'pep': 'pep-reference',
+    u'refer\u00EAncia-a-rfc': 'rfc-reference',
+    'rfc': 'rfc-reference',
+    u'\u00EAnfase': 'emphasis',
+    'forte': 'strong',
+    'literal': 'literal',               # translation required?
+    u'refer\u00EAncia-por-nome': 'named-reference',
+    u'refer\u00EAncia-an\u00F4nima': 'anonymous-reference',
+    u'refer\u00EAncia-a-nota-de-rodap\u00E9': 'footnote-reference',
+    u'refer\u00EAncia-a-cita\u00E7\u00E3o': 'citation-reference',
+    u'refer\u00EAncia-a-substitui\u00E7\u00E3o': 'substitution-reference',
+    'alvo': 'target',
+    u'refer\u00EAncia-a-uri': 'uri-reference',
+    'uri': 'uri-reference',
+    'url': 'uri-reference',
+    'cru': 'raw',}
+"""Mapping of Brazilian Portuguese role names to canonical role names
+for interpreted text."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/ru.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/ru.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/ru.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,98 @@
+# Author: Roman Suzi
+# Contact: rnd at onego.ru
+# Revision: $Revision: 1.1.2.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Russian-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+directives = {
+ u'\u0431\u043b\u043e\u043a-\u0441\u0442\u0440\u043e\u043a': u'line-block',
+ u'meta': u'meta',
+ u'\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u044b\u0439-\u043b\u0438\u0442\u0435\u0440\u0430\u043b':
+ u'parsed-literal',
+ u'\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u043d\u0430\u044f-\u0446\u0438\u0442\u0430\u0442\u0430':
+ u'pull-quote',
+ u'compound (translation required)': 'compound',
+ u'table (translation required)': 'table',
+ u'csv-table (translation required)': 'csv-table',
+ u'\u0441\u044b\u0440\u043e\u0439': u'raw',
+ u'\u0437\u0430\u043c\u0435\u043d\u0430': u'replace',
+ u'\u0442\u0435\u0441\u0442\u043e\u0432\u0430\u044f-\u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0430-restructuredtext':
+ u'restructuredtext-test-directive',
+ u'\u0446\u0435\u043b\u0435\u0432\u044b\u0435-\u0441\u043d\u043e\u0441\u043a\u0438': 
+ u'target-notes',
+ u'unicode': u'unicode',
+ u'\u0431\u043e\u043a\u043e\u0432\u0430\u044f-\u043f\u043e\u043b\u043e\u0441\u0430':
+ u'sidebar',
+ u'\u0432\u0430\u0436\u043d\u043e': u'important',
+ u'\u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c': u'include',
+ u'\u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435': u'attention',
+ u'\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u0435': u'highlights',
+ u'\u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435': u'admonition',
+ u'\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435':
+ u'image',
+ u'\u043a\u043b\u0430\u0441\u0441': u'class',
+ u'role (translation required)': 'role',
+ u'\u043d\u043e\u043c\u0435\u0440-\u0440\u0430\u0437\u0434\u0435\u043b\u0430':
+ u'sectnum',
+ u'\u043d\u0443\u043c\u0435\u0440\u0430\u0446\u0438\u044f-\u0440\u0430\u0437'
+ u'\u0434\u0435\u043b\u043e\u0432': u'sectnum',
+ u'\u043e\u043f\u0430\u0441\u043d\u043e': u'danger',
+ u'\u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u043e': u'caution',
+ u'\u043e\u0448\u0438\u0431\u043a\u0430': u'error',
+ u'\u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430': u'tip',
+ u'\u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d'
+ u'\u0438\u0435': u'warning',
+ u'\u043f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435': u'note',
+ u'\u0440\u0438\u0441\u0443\u043d\u043e\u043a': u'figure',
+ u'\u0440\u0443\u0431\u0440\u0438\u043a\u0430': u'rubric',
+ u'\u0441\u043e\u0432\u0435\u0442': u'hint',
+ u'\u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435': u'contents',
+ u'\u0442\u0435\u043c\u0430': u'topic',
+ u'\u044d\u043f\u0438\u0433\u0440\u0430\u0444': u'epigraph'}
+"""Russian name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+ u'\u0430\u043a\u0440\u043e\u043d\u0438\u043c': 'acronym',
+ u'\u0430\u043d\u043e\u043d\u0438\u043c\u043d\u0430\u044f-\u0441\u0441\u044b\u043b\u043a\u0430':
+  'anonymous-reference',
+ u'\u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e': 'literal',
+ u'\u0432\u0435\u0440\u0445\u043d\u0438\u0439-\u0438\u043d\u0434\u0435\u043a\u0441':
+  'superscript',
+ u'\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u0435': 'emphasis',
+ u'\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u043d\u0430\u044f-\u0441\u0441\u044b\u043b\u043a\u0430':
+  'named-reference',
+ u'\u0438\u043d\u0434\u0435\u043a\u0441': 'index',
+ u'\u043d\u0438\u0436\u043d\u0438\u0439-\u0438\u043d\u0434\u0435\u043a\u0441':
+  'subscript',
+ u'\u0441\u0438\u043b\u044c\u043d\u043e\u0435-\u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u0435':
+  'strong',
+ u'\u0441\u043e\u043a\u0440\u0430\u0449\u0435\u043d\u0438\u0435':
+  'abbreviation',
+ u'\u0441\u0441\u044b\u043b\u043a\u0430-\u0437\u0430\u043c\u0435\u043d\u0430':
+  'substitution-reference',
+ u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-pep': 'pep-reference',
+ u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-rfc': 'rfc-reference',
+ u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-uri': 'uri-reference',
+ u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-\u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435':
+  'title-reference',
+ u'\u0441\u0441\u044b\u043b\u043a\u0430-\u043d\u0430-\u0441\u043d\u043e\u0441\u043a\u0443':
+  'footnote-reference',
+ u'\u0446\u0438\u0442\u0430\u0442\u043d\u0430\u044f-\u0441\u0441\u044b\u043b\u043a\u0430':
+  'citation-reference',
+ u'\u0446\u0435\u043b\u044c': 'target',
+ u'raw (translation required)': 'raw',}
+"""Mapping of Russian role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/sk.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/sk.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/sk.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,86 @@
+# Author: Miroslav Vasko
+# Contact: zemiak at zoznam.sk
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Slovak-language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      u'pozor': 'attention',
+      u'opatrne': 'caution',
+      u'nebezpe\xe8enstvo': 'danger',
+      u'chyba': 'error',
+      u'rada': 'hint',
+      u'd\xf4le\x9eit\xe9': 'important',
+      u'pozn\xe1mka': 'note',
+      u'tip (translation required)': 'tip',
+      u'varovanie': 'warning',
+      u'admonition (translation required)': 'admonition',
+      u'sidebar (translation required)': 'sidebar',
+      u't\xe9ma': 'topic',
+      u'blok-riadkov': 'line-block',
+      u'parsed-literal': 'parsed-literal',
+      u'rubric (translation required)': 'rubric',
+      u'epigraph (translation required)': 'epigraph',
+      u'highlights (translation required)': 'highlights',
+      u'pull-quote (translation required)': 'pull-quote',
+      u'compound (translation required)': 'compound',
+      #u'questions': 'questions',
+      #u'qa': 'questions',
+      #u'faq': 'questions',
+      u'table (translation required)': 'table',
+      u'csv-table (translation required)': 'csv-table',
+      u'meta': 'meta',
+      #u'imagemap': 'imagemap',
+      u'obr\xe1zok': 'image',
+      u'tvar': 'figure',
+      u'vlo\x9ei\x9d': 'include',
+      u'raw (translation required)': 'raw',
+      u'nahradi\x9d': 'replace',
+      u'unicode': 'unicode',
+      u'class (translation required)': 'class',
+      u'role (translation required)': 'role',
+      u'obsah': 'contents',
+      u'\xe8as\x9d': 'sectnum',
+      u'\xe8as\x9d-\xe8\xedslovanie': 'sectnum',
+      u'cie\xbeov\xe9-pozn\xe1mky': 'target-notes',
+      #u'footnotes': 'footnotes',
+      #u'citations': 'citations',
+      }
+"""Slovak name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+      u'abbreviation (translation required)': 'abbreviation',
+      u'acronym (translation required)': 'acronym',
+      u'index (translation required)': 'index',
+      u'subscript (translation required)': 'subscript',
+      u'superscript (translation required)': 'superscript',
+      u'title-reference (translation required)': 'title-reference',
+      u'pep-reference (translation required)': 'pep-reference',
+      u'rfc-reference (translation required)': 'rfc-reference',
+      u'emphasis (translation required)': 'emphasis',
+      u'strong (translation required)': 'strong',
+      u'literal (translation required)': 'literal',
+      u'named-reference (translation required)': 'named-reference',
+      u'anonymous-reference (translation required)': 'anonymous-reference',
+      u'footnote-reference (translation required)': 'footnote-reference',
+      u'citation-reference (translation required)': 'citation-reference',
+      u'substitution-reference (translation required)': 'substitution-reference',
+      u'target (translation required)': 'target',
+      u'uri-reference (translation required)': 'uri-reference',
+      u'raw (translation required)': 'raw',}
+"""Mapping of Slovak role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/sv.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/sv.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/sv.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,85 @@
+# Author:    Adam Chodorowski
+# Contact:   chodorowski at users.sourceforge.net
+# Revision:  $Revision: 1.2.10.7 $
+# Date:      $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Swedish language mappings for language-dependent features of reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      u'observera': 'attention',
+      u'caution (translation required)': 'caution',
+      u'fara': 'danger',
+      u'fel': 'error',
+      u'v\u00e4gledning': 'hint',
+      u'viktigt': 'important',
+      u'notera': 'note',
+      u'tips': 'tip',
+      u'varning': 'warning',
+      u'admonition (translation required)': 'admonition',
+      u'sidebar (translation required)': 'sidebar',
+      u'\u00e4mne': 'topic',
+      u'line-block (translation required)': 'line-block',
+      u'parsed-literal (translation required)': 'parsed-literal',
+      u'mellanrubrik': 'rubric',
+      u'epigraph (translation required)': 'epigraph',
+      u'highlights (translation required)': 'highlights',
+      u'pull-quote (translation required)': 'pull-quote',
+      u'compound (translation required)': 'compound',
+      # u'fr\u00e5gor': 'questions',
+      # NOTE: A bit long, but recommended by http://www.nada.kth.se/dataterm/:
+      # u'fr\u00e5gor-och-svar': 'questions',
+      # u'vanliga-fr\u00e5gor': 'questions',  
+      u'table (translation required)': 'table',
+      u'csv-table (translation required)': 'csv-table',
+      u'meta': 'meta',
+      # u'bildkarta': 'imagemap',   # FIXME: Translation might be too literal.
+      u'bild': 'image',
+      u'figur': 'figure',
+      u'inkludera': 'include',   
+      u'r\u00e5': 'raw',            # FIXME: Translation might be too literal.
+      u'ers\u00e4tt': 'replace', 
+      u'unicode': 'unicode',
+      u'class (translation required)': 'class',
+      u'role (translation required)': 'role',
+      u'inneh\u00e5ll': 'contents',
+      u'sektionsnumrering': 'sectnum',
+      u'target-notes (translation required)': 'target-notes',
+      # u'fotnoter': 'footnotes',
+      # u'citeringar': 'citations',
+      }
+"""Swedish name to registered (in directives/__init__.py) directive name
+mapping."""
+
+roles = {
+      u'abbreviation (translation required)': 'abbreviation',
+      u'acronym (translation required)': 'acronym',
+      u'index (translation required)': 'index',
+      u'subscript (translation required)': 'subscript',
+      u'superscript (translation required)': 'superscript',
+      u'title-reference (translation required)': 'title-reference',
+      u'pep-reference (translation required)': 'pep-reference',
+      u'rfc-reference (translation required)': 'rfc-reference',
+      u'emphasis (translation required)': 'emphasis',
+      u'strong (translation required)': 'strong',
+      u'literal (translation required)': 'literal',
+      u'named-reference (translation required)': 'named-reference',
+      u'anonymous-reference (translation required)': 'anonymous-reference',
+      u'footnote-reference (translation required)': 'footnote-reference',
+      u'citation-reference (translation required)': 'citation-reference',
+      u'substitution-reference (translation required)': 'substitution-reference',
+      u'target (translation required)': 'target',
+      u'uri-reference (translation required)': 'uri-reference',
+      u'r\u00e5': 'raw',}
+"""Mapping of Swedish role names to canonical role names for interpreted text.
+"""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/zh_tw.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/zh_tw.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/languages/zh_tw.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,99 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.1.2.1 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+# New language mappings are welcome.  Before doing a new translation, please
+# read <http://docutils.sf.net/docs/howto/i18n.html>.  Two files must be
+# translated for each language: one in docutils/languages, the other in
+# docutils/parsers/rst/languages.
+
+"""
+Traditional Chinese language mappings for language-dependent features of
+reStructuredText.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+directives = {
+      # language-dependent: fixed
+      'attention (translation required)': 'attention',
+      'caution (translation required)': 'caution',
+      'danger (translation required)': 'danger',
+      'error (translation required)': 'error',
+      'hint (translation required)': 'hint',
+      'important (translation required)': 'important',
+      'note (translation required)': 'note',
+      'tip (translation required)': 'tip',
+      'warning (translation required)': 'warning',
+      'admonition (translation required)': 'admonition',
+      'sidebar (translation required)': 'sidebar',
+      'topic (translation required)': 'topic',
+      'line-block (translation required)': 'line-block',
+      'parsed-literal (translation required)': 'parsed-literal',
+      'rubric (translation required)': 'rubric',
+      'epigraph (translation required)': 'epigraph',
+      'highlights (translation required)': 'highlights',
+      'pull-quote (translation required)': 'pull-quote',
+      'compound (translation required)': 'compound',
+      #'questions (translation required)': 'questions',
+      'table (translation required)': 'table',
+      'csv-table (translation required)': 'csv-table',
+      #'qa (translation required)': 'questions',
+      #'faq (translation required)': 'questions',
+      'meta (translation required)': 'meta',
+      #'imagemap (translation required)': 'imagemap',
+      'image (translation required)': 'image',
+      'figure (translation required)': 'figure',
+      'include (translation required)': 'include',
+      'raw (translation required)': 'raw',
+      'replace (translation required)': 'replace',
+      'unicode (translation required)': 'unicode',
+      'class (translation required)': 'class',
+      'role (translation required)': 'role',
+      'contents (translation required)': 'contents',
+      'sectnum (translation required)': 'sectnum',
+      'section-numbering (translation required)': 'sectnum',
+      #'footnotes (translation required)': 'footnotes',
+      #'citations (translation required)': 'citations',
+      'target-notes (translation required)': 'target-notes',
+      'restructuredtext-test-directive': 'restructuredtext-test-directive'}
+"""Traditional Chinese name to registered (in directives/__init__.py)
+directive name mapping."""
+
+roles = {
+    # language-dependent: fixed
+    'abbreviation (translation required)': 'abbreviation',
+    'ab (translation required)': 'abbreviation',
+    'acronym (translation required)': 'acronym',
+    'ac (translation required)': 'acronym',
+    'index (translation required)': 'index',
+    'i (translation required)': 'index',
+    'subscript (translation required)': 'subscript',
+    'sub (translation required)': 'subscript',
+    'superscript (translation required)': 'superscript',
+    'sup (translation required)': 'superscript',
+    'title-reference (translation required)': 'title-reference',
+    'title (translation required)': 'title-reference',
+    't (translation required)': 'title-reference',
+    'pep-reference (translation required)': 'pep-reference',
+    'pep (translation required)': 'pep-reference',
+    'rfc-reference (translation required)': 'rfc-reference',
+    'rfc (translation required)': 'rfc-reference',
+    'emphasis (translation required)': 'emphasis',
+    'strong (translation required)': 'strong',
+    'literal (translation required)': 'literal',
+    'named-reference (translation required)': 'named-reference',
+    'anonymous-reference (translation required)': 'anonymous-reference',
+    'footnote-reference (translation required)': 'footnote-reference',
+    'citation-reference (translation required)': 'citation-reference',
+    'substitution-reference (translation required)': 'substitution-reference',
+    'target (translation required)': 'target',
+    'uri-reference (translation required)': 'uri-reference',
+    'uri (translation required)': 'uri-reference',
+    'url (translation required)': 'uri-reference',
+    'raw (translation required)': 'raw',}
+"""Mapping of Traditional Chinese role names to canonical role names for
+interpreted text."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/roles.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/roles.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/roles.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,331 @@
+# Author: Edward Loper
+# Contact: edloper at gradient.cis.upenn.edu
+# Revision: $Revision: 1.1.4.4 $
+# Date: $Date: 2005/01/07 13:26:03 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This module defines standard interpreted text role functions, a registry for
+interpreted text roles, and an API for adding to and retrieving from the
+registry.
+
+The interface for interpreted role functions is as follows::
+
+    def role_fn(name, rawtext, text, lineno, inliner,
+                options={}, content=[]):
+        code...
+
+    # Set function attributes for customization:
+    role_fn.options = ...
+    role_fn.content = ...
+
+Parameters:
+
+- ``name`` is the local name of the interpreted text role, the role name
+  actually used in the document.
+
+- ``rawtext`` is a string containing the entire interpreted text construct.
+  Return it as a ``problematic`` node linked to a system message if there is a
+  problem.
+
+- ``text`` is the interpreted text content, with backslash escapes converted
+  to nulls (``\x00``).
+
+- ``lineno`` is the line number where the interpreted text beings.
+
+- ``inliner`` is the Inliner object that called the role function.
+  It defines the following useful attributes: ``reporter``,
+  ``problematic``, ``memo``, ``parent``, ``document``.
+
+- ``options``: A dictionary of directive options for customization, to be
+  interpreted by the role function.  Used for additional attributes for the
+  generated elements and other functionality.
+
+- ``content``: A list of strings, the directive content for customization
+  ("role" directive).  To be interpreted by the role function.
+
+Function attributes for customization, interpreted by the "role" directive:
+
+- ``options``: A dictionary, mapping known option names to conversion
+  functions such as `int` or `float`.  ``None`` or an empty dict implies no
+  options to parse.  Several directive option conversion functions are defined
+  in the `directives` module.
+
+  All role functions implicitly support the "class" option, unless disabled
+  with an explicit ``{'class': None}``.
+
+- ``content``: A boolean; true if content is allowed.  Client code must handle
+  the case where content is required but not supplied (an empty content list
+  will be supplied).
+
+Note that unlike directives, the "arguments" function attribute is not
+supported for role customization.  Directive arguments are handled by the
+"role" directive itself.
+
+Interpreted role functions return a tuple of two values:
+
+- A list of nodes which will be inserted into the document tree at the
+  point where the interpreted role was encountered (can be an empty
+  list).
+
+- A list of system messages, which will be inserted into the document tree
+  immediately after the end of the current inline block (can also be empty).
+"""
+
+__docformat__ = 'reStructuredText'
+
+from docutils import nodes, utils
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.languages import en as _fallback_language_module
+
+DEFAULT_INTERPRETED_ROLE = 'title-reference'
+"""
+The canonical name of the default interpreted role.  This role is used
+when no role is specified for a piece of interpreted text.
+"""
+
+_role_registry = {}
+"""Mapping of canonical role names to role functions.  Language-dependent role
+names are defined in the ``language`` subpackage."""
+
+_roles = {}
+"""Mapping of local or language-dependent interpreted text role names to role
+functions."""
+
+def role(role_name, language_module, lineno, reporter):
+    """
+    Locate and return a role function from its language-dependent name, along
+    with a list of system messages.  If the role is not found in the current
+    language, check English.  Return a 2-tuple: role function (``None`` if the
+    named role cannot be found) and a list of system messages.
+    """
+    normname = role_name.lower()
+    messages = []
+    msg_text = []
+
+    if _roles.has_key(normname):
+        return _roles[normname], messages
+
+    if role_name:
+        canonicalname = None
+        try:
+            canonicalname = language_module.roles[normname]
+        except AttributeError, error:
+            msg_text.append('Problem retrieving role entry from language '
+                            'module %r: %s.' % (language_module, error))
+        except KeyError:
+            msg_text.append('No role entry for "%s" in module "%s".'
+                            % (role_name, language_module.__name__))
+    else:
+        canonicalname = DEFAULT_INTERPRETED_ROLE
+
+    # If we didn't find it, try English as a fallback.
+    if not canonicalname:
+        try:
+            canonicalname = _fallback_language_module.roles[normname]
+            msg_text.append('Using English fallback for role "%s".'
+                            % role_name)
+        except KeyError:
+            msg_text.append('Trying "%s" as canonical role name.'
+                            % role_name)
+            # The canonical name should be an English name, but just in case:
+            canonicalname = normname
+
+    # Collect any messages that we generated.
+    if msg_text:
+        message = reporter.info('\n'.join(msg_text), line=lineno)
+        messages.append(message)
+
+    # Look the role up in the registry, and return it.
+    if _role_registry.has_key(canonicalname):
+        role_fn = _role_registry[canonicalname]
+        register_local_role(normname, role_fn)
+        return role_fn, messages
+    else:
+        return None, messages # Error message will be generated by caller.
+
+def register_canonical_role(name, role_fn):
+    """
+    Register an interpreted text role by its canonical name.
+
+    :Parameters:
+      - `name`: The canonical name of the interpreted role.
+      - `role_fn`: The role function.  See the module docstring.
+    """
+    set_implicit_options(role_fn)
+    _role_registry[name] = role_fn
+
+def register_local_role(name, role_fn):
+    """
+    Register an interpreted text role by its local or language-dependent name.
+
+    :Parameters:
+      - `name`: The local or language-dependent name of the interpreted role.
+      - `role_fn`: The role function.  See the module docstring.
+    """
+    set_implicit_options(role_fn)
+    _roles[name] = role_fn
+
+def set_implicit_options(role_fn):
+    """
+    Add customization options to role functions, unless explicitly set or
+    disabled.
+    """
+    if not hasattr(role_fn, 'options') or role_fn.options is None:
+        role_fn.options = {'class': directives.class_option}
+    elif not role_fn.options.has_key('class'):
+        role_fn.options['class'] = directives.class_option    
+
+def register_generic_role(canonical_name, node_class):
+    """For roles which simply wrap a given `node_class` around the text."""
+    role = GenericRole(canonical_name, node_class)
+    register_canonical_role(canonical_name, role)
+
+
+class GenericRole:
+
+    """
+    Generic interpreted text role, where the interpreted text is simply
+    wrapped with the provided node class.
+    """
+
+    def __init__(self, role_name, node_class):
+        self.name = role_name
+        self.node_class = node_class
+
+    def __call__(self, role, rawtext, text, lineno, inliner,
+                 options={}, content=[]):
+        return [self.node_class(rawtext, utils.unescape(text), **options)], []
+
+
+class CustomRole:
+
+    """
+    Wrapper for custom interpreted text roles.
+    """
+
+    def __init__(self, role_name, base_role, options={}, content=[]):
+        self.name = role_name
+        self.base_role = base_role
+        self.options = None
+        if hasattr(base_role, 'options'):
+            self.options = base_role.options
+        self.content = None
+        if hasattr(base_role, 'content'):
+            self.content = base_role.content
+        self.supplied_options = options
+        self.supplied_content = content
+
+    def __call__(self, role, rawtext, text, lineno, inliner,
+                 options={}, content=[]):
+        opts = self.supplied_options.copy()
+        opts.update(options)
+        cont = list(self.supplied_content)
+        if cont and content:
+            cont += '\n'
+        cont.extend(content)
+        return self.base_role(role, rawtext, text, lineno, inliner,
+                              options=opts, content=cont)
+
+
+def generic_custom_role(role, rawtext, text, lineno, inliner,
+                        options={}, content=[]):
+    """"""
+    # Once nested inline markup is implemented, this and other methods should
+    # recursively call inliner.nested_parse().
+    return [nodes.inline(rawtext, utils.unescape(text), **options)], []
+
+generic_custom_role.options = {'class': directives.class_option}
+
+
+######################################################################
+# Define and register the standard roles:
+######################################################################
+
+register_generic_role('abbreviation', nodes.abbreviation)
+register_generic_role('acronym', nodes.acronym)
+register_generic_role('emphasis', nodes.emphasis)
+register_generic_role('literal', nodes.literal)
+register_generic_role('strong', nodes.strong)
+register_generic_role('subscript', nodes.subscript)
+register_generic_role('superscript', nodes.superscript)
+register_generic_role('title-reference', nodes.title_reference)
+
+def pep_reference_role(role, rawtext, text, lineno, inliner,
+                       options={}, content=[]):
+    try:
+        pepnum = int(text)
+        if pepnum < 0 or pepnum > 9999:
+            raise ValueError
+    except ValueError:
+        msg = inliner.reporter.error(
+            'PEP number must be a number from 0 to 9999; "%s" is invalid.'
+            % text, line=lineno)
+        prb = inliner.problematic(rawtext, rawtext, msg)
+        return [prb], [msg]
+    # Base URL mainly used by inliner.pep_reference; so this is correct:
+    ref = inliner.document.settings.pep_base_url + inliner.pep_url % pepnum
+    return [nodes.reference(rawtext, 'PEP ' + utils.unescape(text), refuri=ref,
+                            **options)], []
+
+register_canonical_role('pep-reference', pep_reference_role)
+
+def rfc_reference_role(role, rawtext, text, lineno, inliner,
+                       options={}, content=[]):
+    try:
+        rfcnum = int(text)
+        if rfcnum <= 0:
+            raise ValueError
+    except ValueError:
+        msg = inliner.reporter.error(
+            'RFC number must be a number greater than or equal to 1; '
+            '"%s" is invalid.' % text, line=lineno)
+        prb = inliner.problematic(rawtext, rawtext, msg)
+        return [prb], [msg]
+    # Base URL mainly used by inliner.rfc_reference, so this is correct:
+    ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum
+    node = nodes.reference(rawtext, 'RFC ' + utils.unescape(text), refuri=ref,
+                           **options)
+    return [node], []
+
+register_canonical_role('rfc-reference', rfc_reference_role)
+
+def raw_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
+    if not options.has_key('format'):
+        msg = inliner.reporter.error(
+            'No format (Writer name) is associated with this role: "%s".\n'
+            'The "raw" role cannot be used directly.\n'
+            'Instead, use the "role" directive to create a new role with '
+            'an associated format.' % role, line=lineno)
+        prb = inliner.problematic(rawtext, rawtext, msg)
+        return [prb], [msg]
+    node = nodes.raw(rawtext, utils.unescape(text, 1), **options)
+    return [node], []
+
+raw_role.options = {'format': directives.class_option}
+
+register_canonical_role('raw', raw_role)
+
+
+######################################################################
+# Register roles that are currently unimplemented.
+######################################################################
+
+def unimplemented_role(role, rawtext, text, lineno, inliner, attributes={}):
+    msg = inliner.reporter.error(
+        'Interpreted text role "%s" not implemented.' % role, line=lineno)
+    prb = inliner.problematic(rawtext, rawtext, msg)
+    return [prb], [msg]
+
+register_canonical_role('index', unimplemented_role)
+register_canonical_role('named-reference', unimplemented_role)
+register_canonical_role('anonymous-reference', unimplemented_role)
+register_canonical_role('uri-reference', unimplemented_role)
+register_canonical_role('footnote-reference', unimplemented_role)
+register_canonical_role('citation-reference', unimplemented_role)
+register_canonical_role('substitution-reference', unimplemented_role)
+register_canonical_role('target', unimplemented_role)
+
+# This should remain unimplemented, for testing purposes:
+register_canonical_role('restructuredtext-unimplemented-role',
+                        unimplemented_role)

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/roman.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/roman.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/roman.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,81 @@
+"""Convert to and from Roman numerals"""
+
+__author__ = "Mark Pilgrim (f8dy at diveintopython.org)"
+__version__ = "1.4"
+__date__ = "8 August 2001"
+__copyright__ = """Copyright (c) 2001 Mark Pilgrim
+
+This program is part of "Dive Into Python", a free Python tutorial for
+experienced programmers.  Visit http://diveintopython.org/ for the
+latest version.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the Python 2.1.1 license, available at
+http://www.python.org/2.1.1/license.html
+"""
+
+import re
+
+#Define exceptions
+class RomanError(Exception): pass
+class OutOfRangeError(RomanError): pass
+class NotIntegerError(RomanError): pass
+class InvalidRomanNumeralError(RomanError): pass
+
+#Define digit mapping
+romanNumeralMap = (('M',  1000),
+                   ('CM', 900),
+                   ('D',  500),
+                   ('CD', 400),
+                   ('C',  100),
+                   ('XC', 90),
+                   ('L',  50),
+                   ('XL', 40),
+                   ('X',  10),
+                   ('IX', 9),
+                   ('V',  5),
+                   ('IV', 4),
+                   ('I',  1))
+
+def toRoman(n):
+    """convert integer to Roman numeral"""
+    if not (0 < n < 5000):
+        raise OutOfRangeError, "number out of range (must be 1..4999)"
+    if int(n) <> n:
+        raise NotIntegerError, "decimals can not be converted"
+
+    result = ""
+    for numeral, integer in romanNumeralMap:
+        while n >= integer:
+            result += numeral
+            n -= integer
+    return result
+
+#Define pattern to detect valid Roman numerals
+romanNumeralPattern = re.compile("""
+    ^                   # beginning of string
+    M{0,4}              # thousands - 0 to 4 M's
+    (CM|CD|D?C{0,3})    # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
+                        #            or 500-800 (D, followed by 0 to 3 C's)
+    (XC|XL|L?X{0,3})    # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
+                        #        or 50-80 (L, followed by 0 to 3 X's)
+    (IX|IV|V?I{0,3})    # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
+                        #        or 5-8 (V, followed by 0 to 3 I's)
+    $                   # end of string
+    """ ,re.VERBOSE)
+
+def fromRoman(s):
+    """convert Roman numeral to integer"""
+    if not s:
+        raise InvalidRomanNumeralError, 'Input can not be blank'
+    if not romanNumeralPattern.search(s):
+        raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s
+
+    result = 0
+    index = 0
+    for numeral, integer in romanNumeralMap:
+        while s[index:index+len(numeral)] == numeral:
+            result += integer
+            index += len(numeral)
+    return result
+

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/states.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/states.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/states.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,2895 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:03 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This is the ``docutils.parsers.restructuredtext.states`` module, the core of
+the reStructuredText parser.  It defines the following:
+
+:Classes:
+    - `RSTStateMachine`: reStructuredText parser's entry point.
+    - `NestedStateMachine`: recursive StateMachine.
+    - `RSTState`: reStructuredText State superclass.
+    - `Inliner`: For parsing inline markup.
+    - `Body`: Generic classifier of the first line of a block.
+    - `SpecializedBody`: Superclass for compound element members.
+    - `BulletList`: Second and subsequent bullet_list list_items
+    - `DefinitionList`: Second+ definition_list_items.
+    - `EnumeratedList`: Second+ enumerated_list list_items.
+    - `FieldList`: Second+ fields.
+    - `OptionList`: Second+ option_list_items.
+    - `RFC2822List`: Second+ RFC2822-style fields.
+    - `ExtensionOptions`: Parses directive option fields.
+    - `Explicit`: Second+ explicit markup constructs.
+    - `SubstitutionDef`: For embedded directives in substitution definitions.
+    - `Text`: Classifier of second line of a text block.
+    - `SpecializedText`: Superclass for continuation lines of Text-variants.
+    - `Definition`: Second line of potential definition_list_item.
+    - `Line`: Second line of overlined section title or transition marker.
+    - `Struct`: An auxiliary collection class.
+
+:Exception classes:
+    - `MarkupError`
+    - `ParserError`
+    - `MarkupMismatch`
+
+:Functions:
+    - `escape2null()`: Return a string, escape-backslashes converted to nulls.
+    - `unescape()`: Return a string, nulls removed or restored to backslashes.
+
+:Attributes:
+    - `state_classes`: set of State classes used with `RSTStateMachine`.
+
+Parser Overview
+===============
+
+The reStructuredText parser is implemented as a recursive state machine,
+examining its input one line at a time.  To understand how the parser works,
+please first become familiar with the `docutils.statemachine` module.  In the
+description below, references are made to classes defined in this module;
+please see the individual classes for details.
+
+Parsing proceeds as follows:
+
+1. The state machine examines each line of input, checking each of the
+   transition patterns of the state `Body`, in order, looking for a match.
+   The implicit transitions (blank lines and indentation) are checked before
+   any others.  The 'text' transition is a catch-all (matches anything).
+
+2. The method associated with the matched transition pattern is called.
+
+   A. Some transition methods are self-contained, appending elements to the
+      document tree (`Body.doctest` parses a doctest block).  The parser's
+      current line index is advanced to the end of the element, and parsing
+      continues with step 1.
+
+   B. Other transition methods trigger the creation of a nested state machine,
+      whose job is to parse a compound construct ('indent' does a block quote,
+      'bullet' does a bullet list, 'overline' does a section [first checking
+      for a valid section header], etc.).
+
+      - In the case of lists and explicit markup, a one-off state machine is
+        created and run to parse contents of the first item.
+
+      - A new state machine is created and its initial state is set to the
+        appropriate specialized state (`BulletList` in the case of the
+        'bullet' transition; see `SpecializedBody` for more detail).  This
+        state machine is run to parse the compound element (or series of
+        explicit markup elements), and returns as soon as a non-member element
+        is encountered.  For example, the `BulletList` state machine ends as
+        soon as it encounters an element which is not a list item of that
+        bullet list.  The optional omission of inter-element blank lines is
+        enabled by this nested state machine.
+
+      - The current line index is advanced to the end of the elements parsed,
+        and parsing continues with step 1.
+
+   C. The result of the 'text' transition depends on the next line of text.
+      The current state is changed to `Text`, under which the second line is
+      examined.  If the second line is:
+
+      - Indented: The element is a definition list item, and parsing proceeds
+        similarly to step 2.B, using the `DefinitionList` state.
+
+      - A line of uniform punctuation characters: The element is a section
+        header; again, parsing proceeds as in step 2.B, and `Body` is still
+        used.
+
+      - Anything else: The element is a paragraph, which is examined for
+        inline markup and appended to the parent element.  Processing
+        continues with step 1.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+import re
+import roman
+from types import TupleType
+from docutils import nodes, statemachine, utils, urischemes
+from docutils import ApplicationError, DataError
+from docutils.statemachine import StateMachineWS, StateWS
+from docutils.nodes import fully_normalize_name as normalize_name
+from docutils.nodes import whitespace_normalize_name
+from docutils.utils import escape2null, unescape
+from docutils.parsers.rst import directives, languages, tableparser, roles
+from docutils.parsers.rst.languages import en as _fallback_language_module
+
+
+class MarkupError(DataError): pass
+class UnknownInterpretedRoleError(DataError): pass
+class InterpretedRoleNotImplementedError(DataError): pass
+class ParserError(ApplicationError): pass
+class MarkupMismatch(Exception): pass
+
+
+class Struct:
+
+    """Stores data attributes for dotted-attribute access."""
+
+    def __init__(self, **keywordargs):
+        self.__dict__.update(keywordargs)
+
+
+class RSTStateMachine(StateMachineWS):
+
+    """
+    reStructuredText's master StateMachine.
+
+    The entry point to reStructuredText parsing is the `run()` method.
+    """
+
+    def run(self, input_lines, document, input_offset=0, match_titles=1,
+            inliner=None):
+        """
+        Parse `input_lines` and modify the `document` node in place.
+
+        Extend `StateMachineWS.run()`: set up parse-global data and
+        run the StateMachine.
+        """
+        self.language = languages.get_language(
+            document.settings.language_code)
+        self.match_titles = match_titles
+        if inliner is None:
+            inliner = Inliner()
+        inliner.init_customizations(document.settings)
+        self.memo = Struct(document=document,
+                           reporter=document.reporter,
+                           language=self.language,
+                           title_styles=[],
+                           section_level=0,
+                           section_bubble_up_kludge=0,
+                           inliner=inliner)
+        self.document = document
+        self.attach_observer(document.note_source)
+        self.reporter = self.memo.reporter
+        self.node = document
+        results = StateMachineWS.run(self, input_lines, input_offset,
+                                     input_source=document['source'])
+        assert results == [], 'RSTStateMachine.run() results should be empty!'
+        self.node = self.memo = None    # remove unneeded references
+
+
+class NestedStateMachine(StateMachineWS):
+
+    """
+    StateMachine run from within other StateMachine runs, to parse nested
+    document structures.
+    """
+
+    def run(self, input_lines, input_offset, memo, node, match_titles=1):
+        """
+        Parse `input_lines` and populate a `docutils.nodes.document` instance.
+
+        Extend `StateMachineWS.run()`: set up document-wide data.
+        """
+        self.match_titles = match_titles
+        self.memo = memo
+        self.document = memo.document
+        self.attach_observer(self.document.note_source)
+        self.reporter = memo.reporter
+        self.language = memo.language
+        self.node = node
+        results = StateMachineWS.run(self, input_lines, input_offset)
+        assert results == [], ('NestedStateMachine.run() results should be '
+                               'empty!')
+        return results
+
+
+class RSTState(StateWS):
+
+    """
+    reStructuredText State superclass.
+
+    Contains methods used by all State subclasses.
+    """
+
+    nested_sm = NestedStateMachine
+
+    def __init__(self, state_machine, debug=0):
+        self.nested_sm_kwargs = {'state_classes': state_classes,
+                                 'initial_state': 'Body'}
+        StateWS.__init__(self, state_machine, debug)
+
+    def runtime_init(self):
+        StateWS.runtime_init(self)
+        memo = self.state_machine.memo
+        self.memo = memo
+        self.reporter = memo.reporter
+        self.inliner = memo.inliner
+        self.document = memo.document
+        self.parent = self.state_machine.node
+
+    def goto_line(self, abs_line_offset):
+        """
+        Jump to input line `abs_line_offset`, ignoring jumps past the end.
+        """
+        try:
+            self.state_machine.goto_line(abs_line_offset)
+        except EOFError:
+            pass
+
+    def no_match(self, context, transitions):
+        """
+        Override `StateWS.no_match` to generate a system message.
+
+        This code should never be run.
+        """
+        self.reporter.severe(
+            'Internal error: no transition pattern match.  State: "%s"; '
+            'transitions: %s; context: %s; current line: %r.'
+            % (self.__class__.__name__, transitions, context,
+               self.state_machine.line),
+            line=self.state_machine.abs_line_number())
+        return context, None, []
+
+    def bof(self, context):
+        """Called at beginning of file."""
+        return [], []
+
+    def nested_parse(self, block, input_offset, node, match_titles=0,
+                     state_machine_class=None, state_machine_kwargs=None):
+        """
+        Create a new StateMachine rooted at `node` and run it over the input
+        `block`.
+        """
+        if state_machine_class is None:
+            state_machine_class = self.nested_sm
+        if state_machine_kwargs is None:
+            state_machine_kwargs = self.nested_sm_kwargs
+        block_length = len(block)
+        state_machine = state_machine_class(debug=self.debug,
+                                            **state_machine_kwargs)
+        state_machine.run(block, input_offset, memo=self.memo,
+                          node=node, match_titles=match_titles)
+        state_machine.unlink()
+        new_offset = state_machine.abs_line_offset()
+        # No `block.parent` implies disconnected -- lines aren't in sync:
+        if block.parent and (len(block) - block_length) != 0:
+            # Adjustment for block if modified in nested parse:
+            self.state_machine.next_line(len(block) - block_length)
+        return new_offset
+
+    def nested_list_parse(self, block, input_offset, node, initial_state,
+                          blank_finish,
+                          blank_finish_state=None,
+                          extra_settings={},
+                          match_titles=0,
+                          state_machine_class=None,
+                          state_machine_kwargs=None):
+        """
+        Create a new StateMachine rooted at `node` and run it over the input
+        `block`. Also keep track of optional intermediate blank lines and the
+        required final one.
+        """
+        if state_machine_class is None:
+            state_machine_class = self.nested_sm
+        if state_machine_kwargs is None:
+            state_machine_kwargs = self.nested_sm_kwargs.copy()
+        state_machine_kwargs['initial_state'] = initial_state
+        state_machine = state_machine_class(debug=self.debug,
+                                            **state_machine_kwargs)
+        if blank_finish_state is None:
+            blank_finish_state = initial_state
+        state_machine.states[blank_finish_state].blank_finish = blank_finish
+        for key, value in extra_settings.items():
+            setattr(state_machine.states[initial_state], key, value)
+        state_machine.run(block, input_offset, memo=self.memo,
+                          node=node, match_titles=match_titles)
+        blank_finish = state_machine.states[blank_finish_state].blank_finish
+        state_machine.unlink()
+        return state_machine.abs_line_offset(), blank_finish
+
+    def section(self, title, source, style, lineno, messages):
+        """Check for a valid subsection and create one if it checks out."""
+        if self.check_subsection(source, style, lineno):
+            self.new_subsection(title, lineno, messages)
+
+    def check_subsection(self, source, style, lineno):
+        """
+        Check for a valid subsection header.  Return 1 (true) or None (false).
+
+        When a new section is reached that isn't a subsection of the current
+        section, back up the line count (use ``previous_line(-x)``), then
+        ``raise EOFError``.  The current StateMachine will finish, then the
+        calling StateMachine can re-examine the title.  This will work its way
+        back up the calling chain until the correct section level isreached.
+
+        @@@ Alternative: Evaluate the title, store the title info & level, and
+        back up the chain until that level is reached.  Store in memo? Or
+        return in results?
+
+        :Exception: `EOFError` when a sibling or supersection encountered.
+        """
+        memo = self.memo
+        title_styles = memo.title_styles
+        mylevel = memo.section_level
+        try:                            # check for existing title style
+            level = title_styles.index(style) + 1
+        except ValueError:              # new title style
+            if len(title_styles) == memo.section_level: # new subsection
+                title_styles.append(style)
+                return 1
+            else:                       # not at lowest level
+                self.parent += self.title_inconsistent(source, lineno)
+                return None
+        if level <= mylevel:            # sibling or supersection
+            memo.section_level = level   # bubble up to parent section
+            if len(style) == 2:
+                memo.section_bubble_up_kludge = 1
+            # back up 2 lines for underline title, 3 for overline title
+            self.state_machine.previous_line(len(style) + 1)
+            raise EOFError              # let parent section re-evaluate
+        if level == mylevel + 1:        # immediate subsection
+            return 1
+        else:                           # invalid subsection
+            self.parent += self.title_inconsistent(source, lineno)
+            return None
+
+    def title_inconsistent(self, sourcetext, lineno):
+        error = self.reporter.severe(
+            'Title level inconsistent:', nodes.literal_block('', sourcetext),
+            line=lineno)
+        return error
+
+    def new_subsection(self, title, lineno, messages):
+        """Append new subsection to document tree. On return, check level."""
+        memo = self.memo
+        mylevel = memo.section_level
+        memo.section_level += 1
+        section_node = nodes.section()
+        self.parent += section_node
+        textnodes, title_messages = self.inline_text(title, lineno)
+        titlenode = nodes.title(title, '', *textnodes)
+        name = normalize_name(titlenode.astext())
+        section_node['name'] = name
+        section_node += titlenode
+        section_node += messages
+        section_node += title_messages
+        self.document.note_implicit_target(section_node, section_node)
+        offset = self.state_machine.line_offset + 1
+        absoffset = self.state_machine.abs_line_offset() + 1
+        newabsoffset = self.nested_parse(
+              self.state_machine.input_lines[offset:], input_offset=absoffset,
+              node=section_node, match_titles=1)
+        self.goto_line(newabsoffset)
+        if memo.section_level <= mylevel: # can't handle next section?
+            raise EOFError              # bubble up to supersection
+        # reset section_level; next pass will detect it properly
+        memo.section_level = mylevel
+
+    def paragraph(self, lines, lineno):
+        """
+        Return a list (paragraph & messages) & a boolean: literal_block next?
+        """
+        data = '\n'.join(lines).rstrip()
+        if data[-2:] == '::':
+            if len(data) == 2:
+                return [], 1
+            elif data[-3] in ' \n':
+                text = data[:-3].rstrip()
+            else:
+                text = data[:-1]
+            literalnext = 1
+        else:
+            text = data
+            literalnext = 0
+        textnodes, messages = self.inline_text(text, lineno)
+        p = nodes.paragraph(data, '', *textnodes)
+        p.line = lineno
+        return [p] + messages, literalnext
+
+    def inline_text(self, text, lineno):
+        """
+        Return 2 lists: nodes (text and inline elements), and system_messages.
+        """
+        return self.inliner.parse(text, lineno, self.memo, self.parent)
+
+    def unindent_warning(self, node_name):
+        return self.reporter.warning(
+            '%s ends without a blank line; unexpected unindent.' % node_name,
+            line=(self.state_machine.abs_line_number() + 1))
+
+
+def build_regexp(definition, compile=1):
+    """
+    Build, compile and return a regular expression based on `definition`.
+
+    :Parameter: `definition`: a 4-tuple (group name, prefix, suffix, parts),
+        where "parts" is a list of regular expressions and/or regular
+        expression definitions to be joined into an or-group.
+    """
+    name, prefix, suffix, parts = definition
+    part_strings = []
+    for part in parts:
+        if type(part) is TupleType:
+            part_strings.append(build_regexp(part, None))
+        else:
+            part_strings.append(part)
+    or_group = '|'.join(part_strings)
+    regexp = '%(prefix)s(?P<%(name)s>%(or_group)s)%(suffix)s' % locals()
+    if compile:
+        return re.compile(regexp, re.UNICODE)
+    else:
+        return regexp
+
+
+class Inliner:
+
+    """
+    Parse inline markup; call the `parse()` method.
+    """
+
+    def __init__(self, roles=None):
+        """
+        `roles` is a mapping of canonical role name to role function or bound
+        method, which enables additional interpreted text roles.
+        """
+
+        self.implicit_dispatch = [(self.patterns.uri, self.standalone_uri),]
+        """List of (pattern, bound method) tuples, used by
+        `self.implicit_inline`."""
+
+    def init_customizations(self, settings):
+        """Setting-based customizations; run when parsing begins."""
+        if settings.pep_references:
+            self.implicit_dispatch.append((self.patterns.pep,
+                                           self.pep_reference))
+        if settings.rfc_references:
+            self.implicit_dispatch.append((self.patterns.rfc,
+                                           self.rfc_reference))
+
+    def parse(self, text, lineno, memo, parent):
+        # Needs to be refactored for nested inline markup.
+        # Add nested_parse() method?
+        """
+        Return 2 lists: nodes (text and inline elements), and system_messages.
+
+        Using `self.patterns.initial`, a pattern which matches start-strings
+        (emphasis, strong, interpreted, phrase reference, literal,
+        substitution reference, and inline target) and complete constructs
+        (simple reference, footnote reference), search for a candidate.  When
+        one is found, check for validity (e.g., not a quoted '*' character).
+        If valid, search for the corresponding end string if applicable, and
+        check it for validity.  If not found or invalid, generate a warning
+        and ignore the start-string.  Implicit inline markup (e.g. standalone
+        URIs) is found last.
+        """
+        self.reporter = memo.reporter
+        self.document = memo.document
+        self.language = memo.language
+        self.parent = parent
+        pattern_search = self.patterns.initial.search
+        dispatch = self.dispatch
+        remaining = escape2null(text)
+        processed = []
+        unprocessed = []
+        messages = []
+        while remaining:
+            match = pattern_search(remaining)
+            if match:
+                groups = match.groupdict()
+                method = dispatch[groups['start'] or groups['backquote']
+                                  or groups['refend'] or groups['fnend']]
+                before, inlines, remaining, sysmessages = method(self, match,
+                                                                 lineno)
+                unprocessed.append(before)
+                messages += sysmessages
+                if inlines:
+                    processed += self.implicit_inline(''.join(unprocessed),
+                                                      lineno)
+                    processed += inlines
+                    unprocessed = []
+            else:
+                break
+        remaining = ''.join(unprocessed) + remaining
+        if remaining:
+            processed += self.implicit_inline(remaining, lineno)
+        return processed, messages
+
+    openers = '\'"([{<'
+    closers = '\'")]}>'
+    start_string_prefix = (r'((?<=^)|(?<=[-/: \n%s]))' % re.escape(openers))
+    end_string_suffix = (r'((?=$)|(?=[-/:.,;!? \n\x00%s]))'
+                         % re.escape(closers))
+    non_whitespace_before = r'(?<![ \n])'
+    non_whitespace_escape_before = r'(?<![ \n\x00])'
+    non_whitespace_after = r'(?![ \n])'
+    # Alphanumerics with isolated internal [-._] chars (i.e. not 2 together):
+    simplename = r'(?:(?!_)\w)+(?:[-._](?:(?!_)\w)+)*'
+    # Valid URI characters (see RFC 2396 & RFC 2732);
+    # final \x00 allows backslash escapes in URIs:
+    uric = r"""[-_.!~*'()[\];/:@&=+$,%a-zA-Z0-9\x00]"""
+    # Delimiter indicating the end of a URI (not part of the URI):
+    uri_end_delim = r"""[>]"""
+    # Last URI character; same as uric but no punctuation:
+    urilast = r"""[_~*/=+a-zA-Z0-9]"""
+    # End of a URI (either 'urilast' or 'uric followed by a
+    # uri_end_delim'):
+    uri_end = r"""(?:%(urilast)s|%(uric)s(?=%(uri_end_delim)s))""" % locals()
+    emailc = r"""[-_!~*'{|}/#?^`&=+$%a-zA-Z0-9\x00]"""
+    email_pattern = r"""
+          %(emailc)s+(?:\.%(emailc)s+)*   # name
+          @                               # at
+          %(emailc)s+(?:\.%(emailc)s*)*   # host
+          %(uri_end)s                     # final URI char
+          """
+    parts = ('initial_inline', start_string_prefix, '',
+             [('start', '', non_whitespace_after,  # simple start-strings
+               [r'\*\*',                # strong
+                r'\*(?!\*)',            # emphasis but not strong
+                r'``',                  # literal
+                r'_`',                  # inline internal target
+                r'\|(?!\|)']            # substitution reference
+               ),
+              ('whole', '', end_string_suffix, # whole constructs
+               [# reference name & end-string
+                r'(?P<refname>%s)(?P<refend>__?)' % simplename,
+                ('footnotelabel', r'\[', r'(?P<fnend>\]_)',
+                 [r'[0-9]+',               # manually numbered
+                  r'\#(%s)?' % simplename, # auto-numbered (w/ label?)
+                  r'\*',                   # auto-symbol
+                  r'(?P<citationlabel>%s)' % simplename] # citation reference
+                 )
+                ]
+               ),
+              ('backquote',             # interpreted text or phrase reference
+               '(?P<role>(:%s:)?)' % simplename, # optional role
+               non_whitespace_after,
+               ['`(?!`)']               # but not literal
+               )
+              ]
+             )
+    patterns = Struct(
+          initial=build_regexp(parts),
+          emphasis=re.compile(non_whitespace_escape_before
+                              + r'(\*)' + end_string_suffix),
+          strong=re.compile(non_whitespace_escape_before
+                            + r'(\*\*)' + end_string_suffix),
+          interpreted_or_phrase_ref=re.compile(
+              r"""
+              %(non_whitespace_escape_before)s
+              (
+                `
+                (?P<suffix>
+                  (?P<role>:%(simplename)s:)?
+                  (?P<refend>__?)?
+                )
+              )
+              %(end_string_suffix)s
+              """ % locals(), re.VERBOSE | re.UNICODE),
+          embedded_uri=re.compile(
+              r"""
+              (
+                (?:[ \n]+|^)            # spaces or beginning of line/string
+                <                       # open bracket
+                %(non_whitespace_after)s
+                ([^<>\x00]+)            # anything but angle brackets & nulls
+                %(non_whitespace_before)s
+                >                       # close bracket w/o whitespace before
+              )
+              $                         # end of string
+              """ % locals(), re.VERBOSE),
+          literal=re.compile(non_whitespace_before + '(``)'
+                             + end_string_suffix),
+          target=re.compile(non_whitespace_escape_before
+                            + r'(`)' + end_string_suffix),
+          substitution_ref=re.compile(non_whitespace_escape_before
+                                      + r'(\|_{0,2})'
+                                      + end_string_suffix),
+          email=re.compile(email_pattern % locals() + '$', re.VERBOSE),
+          uri=re.compile(
+                (r"""
+                %(start_string_prefix)s
+                (?P<whole>
+                  (?P<absolute>           # absolute URI
+                    (?P<scheme>             # scheme (http, ftp, mailto)
+                      [a-zA-Z][a-zA-Z0-9.+-]*
+                    )
+                    :
+                    (
+                      (                       # either:
+                        (//?)?                  # hierarchical URI
+                        %(uric)s*               # URI characters
+                        %(uri_end)s             # final URI char
+                      )
+                      (                       # optional query
+                        \?%(uric)s*
+                        %(uri_end)s
+                      )?
+                      (                       # optional fragment
+                        \#%(uric)s*
+                        %(uri_end)s
+                      )?
+                    )
+                  )
+                |                       # *OR*
+                  (?P<email>              # email address
+                    """ + email_pattern + r"""
+                  )
+                )
+                %(end_string_suffix)s
+                """) % locals(), re.VERBOSE),
+          pep=re.compile(
+                r"""
+                %(start_string_prefix)s
+                (
+                  (pep-(?P<pepnum1>\d+)(.txt)?) # reference to source file
+                |
+                  (PEP\s+(?P<pepnum2>\d+))      # reference by name
+                )
+                %(end_string_suffix)s""" % locals(), re.VERBOSE),
+          rfc=re.compile(
+                r"""
+                %(start_string_prefix)s
+                (RFC(-|\s+)?(?P<rfcnum>\d+))
+                %(end_string_suffix)s""" % locals(), re.VERBOSE))
+
+    def quoted_start(self, match):
+        """Return 1 if inline markup start-string is 'quoted', 0 if not."""
+        string = match.string
+        start = match.start()
+        end = match.end()
+        if start == 0:                  # start-string at beginning of text
+            return 0
+        prestart = string[start - 1]
+        try:
+            poststart = string[end]
+            if self.openers.index(prestart) \
+                  == self.closers.index(poststart):   # quoted
+                return 1
+        except IndexError:              # start-string at end of text
+            return 1
+        except ValueError:              # not quoted
+            pass
+        return 0
+
+    def inline_obj(self, match, lineno, end_pattern, nodeclass,
+                   restore_backslashes=0):
+        string = match.string
+        matchstart = match.start('start')
+        matchend = match.end('start')
+        if self.quoted_start(match):
+            return (string[:matchend], [], string[matchend:], [], '')
+        endmatch = end_pattern.search(string[matchend:])
+        if endmatch and endmatch.start(1):  # 1 or more chars
+            text = unescape(endmatch.string[:endmatch.start(1)],
+                            restore_backslashes)
+            textend = matchend + endmatch.end(1)
+            rawsource = unescape(string[matchstart:textend], 1)
+            return (string[:matchstart], [nodeclass(rawsource, text)],
+                    string[textend:], [], endmatch.group(1))
+        msg = self.reporter.warning(
+              'Inline %s start-string without end-string.'
+              % nodeclass.__name__, line=lineno)
+        text = unescape(string[matchstart:matchend], 1)
+        rawsource = unescape(string[matchstart:matchend], 1)
+        prb = self.problematic(text, rawsource, msg)
+        return string[:matchstart], [prb], string[matchend:], [msg], ''
+
+    def problematic(self, text, rawsource, message):
+        msgid = self.document.set_id(message, self.parent)
+        problematic = nodes.problematic(rawsource, text, refid=msgid)
+        prbid = self.document.set_id(problematic)
+        message.add_backref(prbid)
+        return problematic
+
+    def emphasis(self, match, lineno):
+        before, inlines, remaining, sysmessages, endstring = self.inline_obj(
+              match, lineno, self.patterns.emphasis, nodes.emphasis)
+        return before, inlines, remaining, sysmessages
+
+    def strong(self, match, lineno):
+        before, inlines, remaining, sysmessages, endstring = self.inline_obj(
+              match, lineno, self.patterns.strong, nodes.strong)
+        return before, inlines, remaining, sysmessages
+
+    def interpreted_or_phrase_ref(self, match, lineno):
+        end_pattern = self.patterns.interpreted_or_phrase_ref
+        string = match.string
+        matchstart = match.start('backquote')
+        matchend = match.end('backquote')
+        rolestart = match.start('role')
+        role = match.group('role')
+        position = ''
+        if role:
+            role = role[1:-1]
+            position = 'prefix'
+        elif self.quoted_start(match):
+            return (string[:matchend], [], string[matchend:], [])
+        endmatch = end_pattern.search(string[matchend:])
+        if endmatch and endmatch.start(1):  # 1 or more chars
+            textend = matchend + endmatch.end()
+            if endmatch.group('role'):
+                if role:
+                    msg = self.reporter.warning(
+                        'Multiple roles in interpreted text (both '
+                        'prefix and suffix present; only one allowed).',
+                        line=lineno)
+                    text = unescape(string[rolestart:textend], 1)
+                    prb = self.problematic(text, text, msg)
+                    return string[:rolestart], [prb], string[textend:], [msg]
+                role = endmatch.group('suffix')[1:-1]
+                position = 'suffix'
+            escaped = endmatch.string[:endmatch.start(1)]
+            rawsource = unescape(string[matchstart:textend], 1)
+            if rawsource[-1:] == '_':
+                if role:
+                    msg = self.reporter.warning(
+                          'Mismatch: both interpreted text role %s and '
+                          'reference suffix.' % position, line=lineno)
+                    text = unescape(string[rolestart:textend], 1)
+                    prb = self.problematic(text, text, msg)
+                    return string[:rolestart], [prb], string[textend:], [msg]
+                return self.phrase_ref(string[:matchstart], string[textend:],
+                                       rawsource, escaped, unescape(escaped))
+            else:
+                rawsource = unescape(string[rolestart:textend], 1)
+                nodelist, messages = self.interpreted(rawsource, escaped, role,
+                                                      lineno)
+                return (string[:rolestart], nodelist,
+                        string[textend:], messages)
+        msg = self.reporter.warning(
+              'Inline interpreted text or phrase reference start-string '
+              'without end-string.', line=lineno)
+        text = unescape(string[matchstart:matchend], 1)
+        prb = self.problematic(text, text, msg)
+        return string[:matchstart], [prb], string[matchend:], [msg]
+
+    def phrase_ref(self, before, after, rawsource, escaped, text):
+        match = self.patterns.embedded_uri.search(escaped)
+        if match:
+            text = unescape(escaped[:match.start(0)])
+            uri_text = match.group(2)
+            uri = ''.join(uri_text.split())
+            uri = self.adjust_uri(uri)
+            if uri:
+                target = nodes.target(match.group(1), refuri=uri)
+            else:
+                raise ApplicationError('problem with URI: %r' % uri_text)
+            if not text:
+                text = uri
+        else:
+            target = None
+        refname = normalize_name(text)
+        reference = nodes.reference(rawsource, text,
+                                    name=whitespace_normalize_name(text))
+        node_list = [reference]
+        if rawsource[-2:] == '__':
+            if target:
+                reference['refuri'] = uri
+            else:
+                reference['anonymous'] = 1
+                self.document.note_anonymous_ref(reference)
+        else:
+            if target:
+                reference['refuri'] = uri
+                target['name'] = refname
+                self.document.note_external_target(target)
+                self.document.note_explicit_target(target, self.parent)
+                node_list.append(target)
+            else:
+                reference['refname'] = refname
+                self.document.note_refname(reference)
+        return before, node_list, after, []
+
+    def adjust_uri(self, uri):
+        match = self.patterns.email.match(uri)
+        if match:
+            return 'mailto:' + uri
+        else:
+            return uri
+
+    def interpreted(self, rawsource, text, role, lineno):
+        role_fn, messages = roles.role(role, self.language, lineno,
+                                       self.reporter)
+        if role_fn:
+            nodes, messages2 = role_fn(role, rawsource, text, lineno, self)
+            return nodes, messages + messages2
+        else:
+            msg = self.reporter.error(
+                'Unknown interpreted text role "%s".' % role,
+                line=lineno)
+            return ([self.problematic(rawsource, rawsource, msg)],
+                    messages + [msg])
+
+    def literal(self, match, lineno):
+        before, inlines, remaining, sysmessages, endstring = self.inline_obj(
+              match, lineno, self.patterns.literal, nodes.literal,
+              restore_backslashes=1)
+        return before, inlines, remaining, sysmessages
+
+    def inline_internal_target(self, match, lineno):
+        before, inlines, remaining, sysmessages, endstring = self.inline_obj(
+              match, lineno, self.patterns.target, nodes.target)
+        if inlines and isinstance(inlines[0], nodes.target):
+            assert len(inlines) == 1
+            target = inlines[0]
+            name = normalize_name(target.astext())
+            target['name'] = name
+            self.document.note_explicit_target(target, self.parent)
+        return before, inlines, remaining, sysmessages
+
+    def substitution_reference(self, match, lineno):
+        before, inlines, remaining, sysmessages, endstring = self.inline_obj(
+              match, lineno, self.patterns.substitution_ref,
+              nodes.substitution_reference)
+        if len(inlines) == 1:
+            subref_node = inlines[0]
+            if isinstance(subref_node, nodes.substitution_reference):
+                subref_text = subref_node.astext()
+                self.document.note_substitution_ref(subref_node, subref_text)
+                if endstring[-1:] == '_':
+                    reference_node = nodes.reference(
+                        '|%s%s' % (subref_text, endstring), '')
+                    if endstring[-2:] == '__':
+                        reference_node['anonymous'] = 1
+                        self.document.note_anonymous_ref(
+                              reference_node)
+                    else:
+                        reference_node['refname'] = normalize_name(subref_text)
+                        self.document.note_refname(reference_node)
+                    reference_node += subref_node
+                    inlines = [reference_node]
+        return before, inlines, remaining, sysmessages
+
+    def footnote_reference(self, match, lineno):
+        """
+        Handles `nodes.footnote_reference` and `nodes.citation_reference`
+        elements.
+        """
+        label = match.group('footnotelabel')
+        refname = normalize_name(label)
+        string = match.string
+        before = string[:match.start('whole')]
+        remaining = string[match.end('whole'):]
+        if match.group('citationlabel'):
+            refnode = nodes.citation_reference('[%s]_' % label,
+                                               refname=refname)
+            refnode += nodes.Text(label)
+            self.document.note_citation_ref(refnode)
+        else:
+            refnode = nodes.footnote_reference('[%s]_' % label)
+            if refname[0] == '#':
+                refname = refname[1:]
+                refnode['auto'] = 1
+                self.document.note_autofootnote_ref(refnode)
+            elif refname == '*':
+                refname = ''
+                refnode['auto'] = '*'
+                self.document.note_symbol_footnote_ref(
+                      refnode)
+            else:
+                refnode += nodes.Text(label)
+            if refname:
+                refnode['refname'] = refname
+                self.document.note_footnote_ref(refnode)
+            if utils.get_trim_footnote_ref_space(self.document.settings):
+                before = before.rstrip()
+        return (before, [refnode], remaining, [])
+
+    def reference(self, match, lineno, anonymous=None):
+        referencename = match.group('refname')
+        refname = normalize_name(referencename)
+        referencenode = nodes.reference(
+            referencename + match.group('refend'), referencename,
+            name=whitespace_normalize_name(referencename))
+        if anonymous:
+            referencenode['anonymous'] = 1
+            self.document.note_anonymous_ref(referencenode)
+        else:
+            referencenode['refname'] = refname
+            self.document.note_refname(referencenode)
+        string = match.string
+        matchstart = match.start('whole')
+        matchend = match.end('whole')
+        return (string[:matchstart], [referencenode], string[matchend:], [])
+
+    def anonymous_reference(self, match, lineno):
+        return self.reference(match, lineno, anonymous=1)
+
+    def standalone_uri(self, match, lineno):
+        if not match.group('scheme') or urischemes.schemes.has_key(
+              match.group('scheme').lower()):
+            if match.group('email'):
+                addscheme = 'mailto:'
+            else:
+                addscheme = ''
+            text = match.group('whole')
+            unescaped = unescape(text, 0)
+            return [nodes.reference(unescape(text, 1), unescaped,
+                                    refuri=addscheme + unescaped)]
+        else:                   # not a valid scheme
+            raise MarkupMismatch
+
+    pep_url = 'pep-%04d.html'
+
+    def pep_reference(self, match, lineno):
+        text = match.group(0)
+        if text.startswith('pep-'):
+            pepnum = int(match.group('pepnum1'))
+        elif text.startswith('PEP'):
+            pepnum = int(match.group('pepnum2'))
+        else:
+            raise MarkupMismatch
+        ref = self.document.settings.pep_base_url + self.pep_url % pepnum
+        unescaped = unescape(text, 0)
+        return [nodes.reference(unescape(text, 1), unescaped, refuri=ref)]
+
+    rfc_url = 'rfc%d.html'
+
+    def rfc_reference(self, match, lineno):
+        text = match.group(0)
+        if text.startswith('RFC'):
+            rfcnum = int(match.group('rfcnum'))
+            ref = self.document.settings.rfc_base_url + self.rfc_url % rfcnum
+        else:
+            raise MarkupMismatch
+        unescaped = unescape(text, 0)
+        return [nodes.reference(unescape(text, 1), unescaped, refuri=ref)]
+
+    def implicit_inline(self, text, lineno):
+        """
+        Check each of the patterns in `self.implicit_dispatch` for a match,
+        and dispatch to the stored method for the pattern.  Recursively check
+        the text before and after the match.  Return a list of `nodes.Text`
+        and inline element nodes.
+        """
+        if not text:
+            return []
+        for pattern, method in self.implicit_dispatch:
+            match = pattern.search(text)
+            if match:
+                try:
+                    # Must recurse on strings before *and* after the match;
+                    # there may be multiple patterns.
+                    return (self.implicit_inline(text[:match.start()], lineno)
+                            + method(match, lineno) +
+                            self.implicit_inline(text[match.end():], lineno))
+                except MarkupMismatch:
+                    pass
+        return [nodes.Text(unescape(text), rawsource=unescape(text, 1))]
+
+    dispatch = {'*': emphasis,
+                '**': strong,
+                '`': interpreted_or_phrase_ref,
+                '``': literal,
+                '_`': inline_internal_target,
+                ']_': footnote_reference,
+                '|': substitution_reference,
+                '_': reference,
+                '__': anonymous_reference}
+
+
+class Body(RSTState):
+
+    """
+    Generic classifier of the first line of a block.
+    """
+
+    enum = Struct()
+    """Enumerated list parsing information."""
+
+    enum.formatinfo = {
+          'parens': Struct(prefix='(', suffix=')', start=1, end=-1),
+          'rparen': Struct(prefix='', suffix=')', start=0, end=-1),
+          'period': Struct(prefix='', suffix='.', start=0, end=-1)}
+    enum.formats = enum.formatinfo.keys()
+    enum.sequences = ['arabic', 'loweralpha', 'upperalpha',
+                      'lowerroman', 'upperroman'] # ORDERED!
+    enum.sequencepats = {'arabic': '[0-9]+',
+                         'loweralpha': '[a-z]',
+                         'upperalpha': '[A-Z]',
+                         'lowerroman': '[ivxlcdm]+',
+                         'upperroman': '[IVXLCDM]+',}
+    enum.converters = {'arabic': int,
+                       'loweralpha':
+                       lambda s, zero=(ord('a')-1): ord(s) - zero,
+                       'upperalpha':
+                       lambda s, zero=(ord('A')-1): ord(s) - zero,
+                       'lowerroman':
+                       lambda s: roman.fromRoman(s.upper()),
+                       'upperroman': roman.fromRoman}
+
+    enum.sequenceregexps = {}
+    for sequence in enum.sequences:
+        enum.sequenceregexps[sequence] = re.compile(
+              enum.sequencepats[sequence] + '$')
+
+    grid_table_top_pat = re.compile(r'\+-[-+]+-\+ *$')
+    """Matches the top (& bottom) of a full table)."""
+
+    simple_table_top_pat = re.compile('=+( +=+)+ *$')
+    """Matches the top of a simple table."""
+
+    simple_table_border_pat = re.compile('=+[ =]*$')
+    """Matches the bottom & header bottom of a simple table."""
+
+    pats = {}
+    """Fragments of patterns used by transitions."""
+
+    pats['nonalphanum7bit'] = '[!-/:-@[-`{-~]'
+    pats['alpha'] = '[a-zA-Z]'
+    pats['alphanum'] = '[a-zA-Z0-9]'
+    pats['alphanumplus'] = '[a-zA-Z0-9_-]'
+    pats['enum'] = ('(%(arabic)s|%(loweralpha)s|%(upperalpha)s|%(lowerroman)s'
+                    '|%(upperroman)s)' % enum.sequencepats)
+    pats['optname'] = '%(alphanum)s%(alphanumplus)s*' % pats
+    # @@@ Loosen up the pattern?  Allow Unicode?
+    pats['optarg'] = '(%(alpha)s%(alphanumplus)s*|<%(alphanum)s[^ <>]+>)' % pats
+    pats['shortopt'] = r'(-|\+)%(alphanum)s( ?%(optarg)s)?' % pats
+    pats['longopt'] = r'(--|/)%(optname)s([ =]%(optarg)s)?' % pats
+    pats['option'] = r'(%(shortopt)s|%(longopt)s)' % pats
+
+    for format in enum.formats:
+        pats[format] = '(?P<%s>%s%s%s)' % (
+              format, re.escape(enum.formatinfo[format].prefix),
+              pats['enum'], re.escape(enum.formatinfo[format].suffix))
+
+    patterns = {
+          'bullet': r'[-+*]( +|$)',
+          'enumerator': r'(%(parens)s|%(rparen)s|%(period)s)( +|$)' % pats,
+          'field_marker': r':[^: ]([^:]*[^: ])?:( +|$)',
+          'option_marker': r'%(option)s(, %(option)s)*(  +| ?$)' % pats,
+          'doctest': r'>>>( +|$)',
+          'line_block': r'\|( +|$)',
+          'grid_table_top': grid_table_top_pat,
+          'simple_table_top': simple_table_top_pat,
+          'explicit_markup': r'\.\.( +|$)',
+          'anonymous': r'__( +|$)',
+          'line': r'(%(nonalphanum7bit)s)\1* *$' % pats,
+          'text': r''}
+    initial_transitions = (
+          'bullet',
+          'enumerator',
+          'field_marker',
+          'option_marker',
+          'doctest',
+          'line_block',
+          'grid_table_top',
+          'simple_table_top',
+          'explicit_markup',
+          'anonymous',
+          'line',
+          'text')
+
+    def indent(self, match, context, next_state):
+        """Block quote."""
+        indented, indent, line_offset, blank_finish = \
+              self.state_machine.get_indented()
+        blockquote, messages = self.block_quote(indented, line_offset)
+        self.parent += blockquote
+        self.parent += messages
+        if not blank_finish:
+            self.parent += self.unindent_warning('Block quote')
+        return context, next_state, []
+
+    def block_quote(self, indented, line_offset):
+        blockquote_lines, attribution_lines, attribution_offset = \
+              self.check_attribution(indented, line_offset)
+        blockquote = nodes.block_quote()
+        self.nested_parse(blockquote_lines, line_offset, blockquote)
+        messages = []
+        if attribution_lines:
+            attribution, messages = self.parse_attribution(attribution_lines,
+                                                           attribution_offset)
+            blockquote += attribution
+        return blockquote, messages
+
+    # u'\u2014' is an em-dash:
+    attribution_pattern = re.compile(ur'(---?(?!-)|\u2014) *(?=[^ \n])')
+
+    def check_attribution(self, indented, line_offset):
+        """
+        Check for an attribution in the last contiguous block of `indented`.
+
+        * First line after last blank line must begin with "--" (etc.).
+        * Every line after that must have consistent indentation.
+
+        Return a 3-tuple: (block quote lines, attribution lines,
+        attribution offset).
+        """
+        blank = None
+        nonblank_seen = None
+        indent = 0
+        for i in range(len(indented) - 1, 0, -1): # don't check first line
+            this_line_blank = not indented[i].strip()
+            if nonblank_seen and this_line_blank:
+                match = self.attribution_pattern.match(indented[i + 1])
+                if match:
+                    blank = i
+                break
+            elif not this_line_blank:
+                nonblank_seen = 1
+        if blank and len(indented) - blank > 2: # multi-line attribution
+            indent = (len(indented[blank + 2])
+                      - len(indented[blank + 2].lstrip()))
+            for j in range(blank + 3, len(indented)):
+                if indent != (len(indented[j])
+                              - len(indented[j].lstrip())): # bad shape
+                    blank = None
+                    break
+        if blank:
+            a_lines = indented[blank + 1:]
+            a_lines.trim_left(match.end(), end=1)
+            a_lines.trim_left(indent, start=1)
+            return (indented[:blank], a_lines, line_offset + blank + 1)
+        else:
+            return (indented, None, None)
+
+    def parse_attribution(self, indented, line_offset):
+        text = '\n'.join(indented).rstrip()
+        lineno = self.state_machine.abs_line_number() + line_offset
+        textnodes, messages = self.inline_text(text, lineno)
+        node = nodes.attribution(text, '', *textnodes)
+        node.line = lineno
+        return node, messages
+
+    def bullet(self, match, context, next_state):
+        """Bullet list item."""
+        bulletlist = nodes.bullet_list()
+        self.parent += bulletlist
+        bulletlist['bullet'] = match.string[0]
+        i, blank_finish = self.list_item(match.end())
+        bulletlist += i
+        offset = self.state_machine.line_offset + 1   # next line
+        new_line_offset, blank_finish = self.nested_list_parse(
+              self.state_machine.input_lines[offset:],
+              input_offset=self.state_machine.abs_line_offset() + 1,
+              node=bulletlist, initial_state='BulletList',
+              blank_finish=blank_finish)
+        self.goto_line(new_line_offset)
+        if not blank_finish:
+            self.parent += self.unindent_warning('Bullet list')
+        return [], next_state, []
+
+    def list_item(self, indent):
+        indented, line_offset, blank_finish = \
+              self.state_machine.get_known_indented(indent)
+        listitem = nodes.list_item('\n'.join(indented))
+        if indented:
+            self.nested_parse(indented, input_offset=line_offset,
+                              node=listitem)
+        return listitem, blank_finish
+
+    def enumerator(self, match, context, next_state):
+        """Enumerated List Item"""
+        format, sequence, text, ordinal = self.parse_enumerator(match)
+        if not self.is_enumerated_list_item(ordinal, sequence, format):
+            raise statemachine.TransitionCorrection('text')
+        enumlist = nodes.enumerated_list()
+        self.parent += enumlist
+        enumlist['enumtype'] = sequence
+        enumlist['prefix'] = self.enum.formatinfo[format].prefix
+        enumlist['suffix'] = self.enum.formatinfo[format].suffix
+        if ordinal != 1:
+            enumlist['start'] = ordinal
+            msg = self.reporter.info(
+                'Enumerated list start value not ordinal-1: "%s" (ordinal %s)'
+                % (text, ordinal), line=self.state_machine.abs_line_number())
+            self.parent += msg
+        listitem, blank_finish = self.list_item(match.end())
+        enumlist += listitem
+        offset = self.state_machine.line_offset + 1   # next line
+        newline_offset, blank_finish = self.nested_list_parse(
+              self.state_machine.input_lines[offset:],
+              input_offset=self.state_machine.abs_line_offset() + 1,
+              node=enumlist, initial_state='EnumeratedList',
+              blank_finish=blank_finish,
+              extra_settings={'lastordinal': ordinal, 'format': format})
+        self.goto_line(newline_offset)
+        if not blank_finish:
+            self.parent += self.unindent_warning('Enumerated list')
+        return [], next_state, []
+
+    def parse_enumerator(self, match, expected_sequence=None):
+        """
+        Analyze an enumerator and return the results.
+
+        :Return:
+            - the enumerator format ('period', 'parens', or 'rparen'),
+            - the sequence used ('arabic', 'loweralpha', 'upperroman', etc.),
+            - the text of the enumerator, stripped of formatting, and
+            - the ordinal value of the enumerator ('a' -> 1, 'ii' -> 2, etc.;
+              ``None`` is returned for invalid enumerator text).
+
+        The enumerator format has already been determined by the regular
+        expression match. If `expected_sequence` is given, that sequence is
+        tried first. If not, we check for Roman numeral 1. This way,
+        single-character Roman numerals (which are also alphabetical) can be
+        matched. If no sequence has been matched, all sequences are checked in
+        order.
+        """
+        groupdict = match.groupdict()
+        sequence = ''
+        for format in self.enum.formats:
+            if groupdict[format]:       # was this the format matched?
+                break                   # yes; keep `format`
+        else:                           # shouldn't happen
+            raise ParserError('enumerator format not matched')
+        text = groupdict[format][self.enum.formatinfo[format].start
+                                 :self.enum.formatinfo[format].end]
+        if expected_sequence:
+            try:
+                if self.enum.sequenceregexps[expected_sequence].match(text):
+                    sequence = expected_sequence
+            except KeyError:            # shouldn't happen
+                raise ParserError('unknown enumerator sequence: %s'
+                                  % sequence)
+        elif text == 'i':
+            sequence = 'lowerroman'
+        elif text == 'I':
+            sequence = 'upperroman'
+        if not sequence:
+            for sequence in self.enum.sequences:
+                if self.enum.sequenceregexps[sequence].match(text):
+                    break
+            else:                       # shouldn't happen
+                raise ParserError('enumerator sequence not matched')
+        try:
+            ordinal = self.enum.converters[sequence](text)
+        except roman.InvalidRomanNumeralError:
+            ordinal = None
+        return format, sequence, text, ordinal
+
+    def is_enumerated_list_item(self, ordinal, sequence, format):
+        """
+        Check validity based on the ordinal value and the second line.
+
+        Return true iff the ordinal is valid and the second line is blank,
+        indented, or starts with the next enumerator.
+        """
+        if ordinal is None:
+            return None
+        try:
+            next_line = self.state_machine.next_line()
+        except EOFError:              # end of input lines
+            self.state_machine.previous_line()
+            return 1
+        else:
+            self.state_machine.previous_line()
+        if not next_line[:1].strip():   # blank or indented
+            return 1
+        next_enumerator = self.make_enumerator(ordinal + 1, sequence, format)
+        try:
+            if next_line.startswith(next_enumerator):
+                return 1
+        except TypeError:
+            pass
+        return None
+
+    def make_enumerator(self, ordinal, sequence, format):
+        """
+        Construct and return an enumerated list item marker.
+
+        Return ``None`` for invalid (out of range) ordinals.
+        """
+        if sequence == 'arabic':
+            enumerator = str(ordinal)
+        else:
+            if sequence.endswith('alpha'):
+                if ordinal > 26:
+                    return None
+                enumerator = chr(ordinal + ord('a') - 1)
+            elif sequence.endswith('roman'):
+                try:
+                    enumerator = roman.toRoman(ordinal)
+                except roman.RomanError:
+                    return None
+            else:                       # shouldn't happen
+                raise ParserError('unknown enumerator sequence: "%s"'
+                                  % sequence)
+            if sequence.startswith('lower'):
+                enumerator = enumerator.lower()
+            elif sequence.startswith('upper'):
+                enumerator = enumerator.upper()
+            else:                       # shouldn't happen
+                raise ParserError('unknown enumerator sequence: "%s"'
+                                  % sequence)
+        formatinfo = self.enum.formatinfo[format]
+        return formatinfo.prefix + enumerator + formatinfo.suffix + ' '
+
+    def field_marker(self, match, context, next_state):
+        """Field list item."""
+        field_list = nodes.field_list()
+        self.parent += field_list
+        field, blank_finish = self.field(match)
+        field_list += field
+        offset = self.state_machine.line_offset + 1   # next line
+        newline_offset, blank_finish = self.nested_list_parse(
+              self.state_machine.input_lines[offset:],
+              input_offset=self.state_machine.abs_line_offset() + 1,
+              node=field_list, initial_state='FieldList',
+              blank_finish=blank_finish)
+        self.goto_line(newline_offset)
+        if not blank_finish:
+            self.parent += self.unindent_warning('Field list')
+        return [], next_state, []
+
+    def field(self, match):
+        name = self.parse_field_marker(match)
+        lineno = self.state_machine.abs_line_number()
+        indented, indent, line_offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end())
+        field_node = nodes.field()
+        field_node.line = lineno
+        name_nodes, name_messages = self.inline_text(name, lineno)
+        field_node += nodes.field_name(name, '', *name_nodes)
+        field_body = nodes.field_body('\n'.join(indented), *name_messages)
+        field_node += field_body
+        if indented:
+            self.parse_field_body(indented, line_offset, field_body)
+        return field_node, blank_finish
+
+    def parse_field_marker(self, match):
+        """Extract & return field name from a field marker match."""
+        field = match.string[1:]        # strip off leading ':'
+        field = field[:field.find(':')] # strip off trailing ':' etc.
+        return field
+
+    def parse_field_body(self, indented, offset, node):
+        self.nested_parse(indented, input_offset=offset, node=node)
+
+    def option_marker(self, match, context, next_state):
+        """Option list item."""
+        optionlist = nodes.option_list()
+        try:
+            listitem, blank_finish = self.option_list_item(match)
+        except MarkupError, (message, lineno):
+            # This shouldn't happen; pattern won't match.
+            msg = self.reporter.error(
+                'Invalid option list marker: %s' % message, line=lineno)
+            self.parent += msg
+            indented, indent, line_offset, blank_finish = \
+                  self.state_machine.get_first_known_indented(match.end())
+            blockquote, messages = self.block_quote(indented, line_offset)
+            self.parent += blockquote
+            self.parent += messages
+            if not blank_finish:
+                self.parent += self.unindent_warning('Option list')
+            return [], next_state, []
+        self.parent += optionlist
+        optionlist += listitem
+        offset = self.state_machine.line_offset + 1   # next line
+        newline_offset, blank_finish = self.nested_list_parse(
+              self.state_machine.input_lines[offset:],
+              input_offset=self.state_machine.abs_line_offset() + 1,
+              node=optionlist, initial_state='OptionList',
+              blank_finish=blank_finish)
+        self.goto_line(newline_offset)
+        if not blank_finish:
+            self.parent += self.unindent_warning('Option list')
+        return [], next_state, []
+
+    def option_list_item(self, match):
+        offset = self.state_machine.abs_line_offset()
+        options = self.parse_option_marker(match)
+        indented, indent, line_offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end())
+        if not indented:                # not an option list item
+            self.goto_line(offset)
+            raise statemachine.TransitionCorrection('text')
+        option_group = nodes.option_group('', *options)
+        description = nodes.description('\n'.join(indented))
+        option_list_item = nodes.option_list_item('', option_group,
+                                                  description)
+        if indented:
+            self.nested_parse(indented, input_offset=line_offset,
+                              node=description)
+        return option_list_item, blank_finish
+
+    def parse_option_marker(self, match):
+        """
+        Return a list of `node.option` and `node.option_argument` objects,
+        parsed from an option marker match.
+
+        :Exception: `MarkupError` for invalid option markers.
+        """
+        optlist = []
+        optionstrings = match.group().rstrip().split(', ')
+        for optionstring in optionstrings:
+            tokens = optionstring.split()
+            delimiter = ' '
+            firstopt = tokens[0].split('=')
+            if len(firstopt) > 1:
+                tokens[:1] = firstopt
+                delimiter = '='
+            elif (len(tokens[0]) > 2
+                  and ((tokens[0].startswith('-')
+                        and not tokens[0].startswith('--'))
+                       or tokens[0].startswith('+'))):
+                tokens[:1] = [tokens[0][:2], tokens[0][2:]]
+                delimiter = ''
+            if 0 < len(tokens) <= 2:
+                option = nodes.option(optionstring)
+                option += nodes.option_string(tokens[0], tokens[0])
+                if len(tokens) > 1:
+                    option += nodes.option_argument(tokens[1], tokens[1],
+                                                    delimiter=delimiter)
+                optlist.append(option)
+            else:
+                raise MarkupError(
+                    'wrong numer of option tokens (=%s), should be 1 or 2: '
+                    '"%s"' % (len(tokens), optionstring),
+                    self.state_machine.abs_line_number() + 1)
+        return optlist
+
+    def doctest(self, match, context, next_state):
+        data = '\n'.join(self.state_machine.get_text_block())
+        self.parent += nodes.doctest_block(data, data)
+        return [], next_state, []
+
+    def line_block(self, match, context, next_state):
+        """First line of a line block."""
+        block = nodes.line_block()
+        self.parent += block
+        lineno = self.state_machine.abs_line_number()
+        line, messages, blank_finish = self.line_block_line(match, lineno)
+        block += line
+        self.parent += messages
+        if not blank_finish:
+            offset = self.state_machine.line_offset + 1   # next line
+            new_line_offset, blank_finish = self.nested_list_parse(
+                  self.state_machine.input_lines[offset:],
+                  input_offset=self.state_machine.abs_line_offset() + 1,
+                  node=block, initial_state='LineBlock',
+                  blank_finish=0)
+            self.goto_line(new_line_offset)
+        if not blank_finish:
+            self.parent += self.reporter.warning(
+                'Line block ends without a blank line.',
+                line=(self.state_machine.abs_line_number() + 1))
+        if len(block):
+            if block[0].indent is None:
+                block[0].indent = 0
+            self.nest_line_block_lines(block)
+        return [], next_state, []
+
+    def line_block_line(self, match, lineno):
+        """Return one line element of a line_block."""
+        indented, indent, line_offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end(),
+                                                          until_blank=1)
+        text = u'\n'.join(indented)
+        text_nodes, messages = self.inline_text(text, lineno)
+        line = nodes.line(text, '', *text_nodes)
+        if match.string.rstrip() != '|': # not empty
+            line.indent = len(match.group(1)) - 1
+        return line, messages, blank_finish
+
+    def nest_line_block_lines(self, block):
+        for index in range(1, len(block)):
+            if block[index].indent is None:
+                block[index].indent = block[index - 1].indent
+        self.nest_line_block_segment(block)
+
+    def nest_line_block_segment(self, block):
+        indents = [item.indent for item in block]
+        least = min(indents)
+        new_items = []
+        new_block = nodes.line_block()
+        for item in block:
+            if item.indent > least:
+                new_block.append(item)
+            else:
+                if len(new_block):
+                    self.nest_line_block_segment(new_block)
+                    new_items.append(new_block)
+                    new_block = nodes.line_block()
+                new_items.append(item)
+        if len(new_block):
+            self.nest_line_block_segment(new_block)
+            new_items.append(new_block)
+        block[:] = new_items
+
+    def grid_table_top(self, match, context, next_state):
+        """Top border of a full table."""
+        return self.table_top(match, context, next_state,
+                              self.isolate_grid_table,
+                              tableparser.GridTableParser)
+
+    def simple_table_top(self, match, context, next_state):
+        """Top border of a simple table."""
+        return self.table_top(match, context, next_state,
+                              self.isolate_simple_table,
+                              tableparser.SimpleTableParser)
+
+    def table_top(self, match, context, next_state,
+                  isolate_function, parser_class):
+        """Top border of a generic table."""
+        nodelist, blank_finish = self.table(isolate_function, parser_class)
+        self.parent += nodelist
+        if not blank_finish:
+            msg = self.reporter.warning(
+                'Blank line required after table.',
+                line=self.state_machine.abs_line_number() + 1)
+            self.parent += msg
+        return [], next_state, []
+
+    def table(self, isolate_function, parser_class):
+        """Parse a table."""
+        block, messages, blank_finish = isolate_function()
+        if block:
+            try:
+                parser = parser_class()
+                tabledata = parser.parse(block)
+                tableline = (self.state_machine.abs_line_number() - len(block)
+                             + 1)
+                table = self.build_table(tabledata, tableline)
+                nodelist = [table] + messages
+            except tableparser.TableMarkupError, detail:
+                nodelist = self.malformed_table(block, str(detail)) + messages
+        else:
+            nodelist = messages
+        return nodelist, blank_finish
+
+    def isolate_grid_table(self):
+        messages = []
+        blank_finish = 1
+        try:
+            block = self.state_machine.get_text_block(flush_left=1)
+        except statemachine.UnexpectedIndentationError, instance:
+            block, source, lineno = instance.args
+            messages.append(self.reporter.error('Unexpected indentation.',
+                                                source=source, line=lineno))
+            blank_finish = 0
+        block.disconnect()
+        width = len(block[0].strip())
+        for i in range(len(block)):
+            block[i] = block[i].strip()
+            if block[i][0] not in '+|': # check left edge
+                blank_finish = 0
+                self.state_machine.previous_line(len(block) - i)
+                del block[i:]
+                break
+        if not self.grid_table_top_pat.match(block[-1]): # find bottom
+            blank_finish = 0
+            # from second-last to third line of table:
+            for i in range(len(block) - 2, 1, -1):
+                if self.grid_table_top_pat.match(block[i]):
+                    self.state_machine.previous_line(len(block) - i + 1)
+                    del block[i+1:]
+                    break
+            else:
+                messages.extend(self.malformed_table(block))
+                return [], messages, blank_finish
+        for i in range(len(block)):     # check right edge
+            if len(block[i]) != width or block[i][-1] not in '+|':
+                messages.extend(self.malformed_table(block))
+                return [], messages, blank_finish
+        return block, messages, blank_finish
+
+    def isolate_simple_table(self):
+        start = self.state_machine.line_offset
+        lines = self.state_machine.input_lines
+        limit = len(lines) - 1
+        toplen = len(lines[start].strip())
+        pattern_match = self.simple_table_border_pat.match
+        found = 0
+        found_at = None
+        i = start + 1
+        while i <= limit:
+            line = lines[i]
+            match = pattern_match(line)
+            if match:
+                if len(line.strip()) != toplen:
+                    self.state_machine.next_line(i - start)
+                    messages = self.malformed_table(
+                        lines[start:i+1], 'Bottom/header table border does '
+                        'not match top border.')
+                    return [], messages, i == limit or not lines[i+1].strip()
+                found += 1
+                found_at = i
+                if found == 2 or i == limit or not lines[i+1].strip():
+                    end = i
+                    break
+            i += 1
+        else:                           # reached end of input_lines
+            if found:
+                extra = ' or no blank line after table bottom'
+                self.state_machine.next_line(found_at - start)
+                block = lines[start:found_at+1]
+            else:
+                extra = ''
+                self.state_machine.next_line(i - start - 1)
+                block = lines[start:]
+            messages = self.malformed_table(
+                block, 'No bottom table border found%s.' % extra)
+            return [], messages, not extra
+        self.state_machine.next_line(end - start)
+        block = lines[start:end+1]
+        return block, [], end == limit or not lines[end+1].strip()
+
+    def malformed_table(self, block, detail=''):
+        data = '\n'.join(block)
+        message = 'Malformed table.'
+        lineno = self.state_machine.abs_line_number() - len(block) + 1
+        if detail:
+            message += '\n' + detail
+        error = self.reporter.error(message, nodes.literal_block(data, data),
+                                    line=lineno)
+        return [error]
+
+    def build_table(self, tabledata, tableline):
+        colspecs, headrows, bodyrows = tabledata
+        table = nodes.table()
+        tgroup = nodes.tgroup(cols=len(colspecs))
+        table += tgroup
+        for colspec in colspecs:
+            tgroup += nodes.colspec(colwidth=colspec)
+        if headrows:
+            thead = nodes.thead()
+            tgroup += thead
+            for row in headrows:
+                thead += self.build_table_row(row, tableline)
+        tbody = nodes.tbody()
+        tgroup += tbody
+        for row in bodyrows:
+            tbody += self.build_table_row(row, tableline)
+        return table
+
+    def build_table_row(self, rowdata, tableline):
+        row = nodes.row()
+        for cell in rowdata:
+            if cell is None:
+                continue
+            morerows, morecols, offset, cellblock = cell
+            attributes = {}
+            if morerows:
+                attributes['morerows'] = morerows
+            if morecols:
+                attributes['morecols'] = morecols
+            entry = nodes.entry(**attributes)
+            row += entry
+            if ''.join(cellblock):
+                self.nested_parse(cellblock, input_offset=tableline+offset,
+                                  node=entry)
+        return row
+
+
+    explicit = Struct()
+    """Patterns and constants used for explicit markup recognition."""
+
+    explicit.patterns = Struct(
+          target=re.compile(r"""
+                            (
+                              _               # anonymous target
+                            |               # *OR*
+                              (?P<quote>`?)   # optional open quote
+                              (?![ `])        # first char. not space or
+                                              # backquote
+                              (?P<name>       # reference name
+                                .+?
+                              )
+                              %(non_whitespace_escape_before)s
+                              (?P=quote)      # close quote if open quote used
+                            )
+                            %(non_whitespace_escape_before)s
+                            [ ]?            # optional space
+                            :               # end of reference name
+                            ([ ]+|$)        # followed by whitespace
+                            """ % vars(Inliner), re.VERBOSE),
+          reference=re.compile(r"""
+                               (
+                                 (?P<simple>%(simplename)s)_
+                               |                  # *OR*
+                                 `                  # open backquote
+                                 (?![ ])            # not space
+                                 (?P<phrase>.+?)    # hyperlink phrase
+                                 %(non_whitespace_escape_before)s
+                                 `_                 # close backquote,
+                                                    # reference mark
+                               )
+                               $                  # end of string
+                               """ % vars(Inliner), re.VERBOSE | re.UNICODE),
+          substitution=re.compile(r"""
+                                  (
+                                    (?![ ])          # first char. not space
+                                    (?P<name>.+?)    # substitution text
+                                    %(non_whitespace_escape_before)s
+                                    \|               # close delimiter
+                                  )
+                                  ([ ]+|$)           # followed by whitespace
+                                  """ % vars(Inliner), re.VERBOSE),)
+
+    def footnote(self, match):
+        lineno = self.state_machine.abs_line_number()
+        indented, indent, offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end())
+        label = match.group(1)
+        name = normalize_name(label)
+        footnote = nodes.footnote('\n'.join(indented))
+        footnote.line = lineno
+        if name[0] == '#':              # auto-numbered
+            name = name[1:]             # autonumber label
+            footnote['auto'] = 1
+            if name:
+                footnote['name'] = name
+            self.document.note_autofootnote(footnote)
+        elif name == '*':               # auto-symbol
+            name = ''
+            footnote['auto'] = '*'
+            self.document.note_symbol_footnote(footnote)
+        else:                           # manually numbered
+            footnote += nodes.label('', label)
+            footnote['name'] = name
+            self.document.note_footnote(footnote)
+        if name:
+            self.document.note_explicit_target(footnote, footnote)
+        else:
+            self.document.set_id(footnote, footnote)
+        if indented:
+            self.nested_parse(indented, input_offset=offset, node=footnote)
+        return [footnote], blank_finish
+
+    def citation(self, match):
+        lineno = self.state_machine.abs_line_number()
+        indented, indent, offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end())
+        label = match.group(1)
+        name = normalize_name(label)
+        citation = nodes.citation('\n'.join(indented))
+        citation.line = lineno
+        citation += nodes.label('', label)
+        citation['name'] = name
+        self.document.note_citation(citation)
+        self.document.note_explicit_target(citation, citation)
+        if indented:
+            self.nested_parse(indented, input_offset=offset, node=citation)
+        return [citation], blank_finish
+
+    def hyperlink_target(self, match):
+        pattern = self.explicit.patterns.target
+        lineno = self.state_machine.abs_line_number()
+        block, indent, offset, blank_finish = \
+              self.state_machine.get_first_known_indented(
+              match.end(), until_blank=1, strip_indent=0)
+        blocktext = match.string[:match.end()] + '\n'.join(block)
+        block = [escape2null(line) for line in block]
+        escaped = block[0]
+        blockindex = 0
+        while 1:
+            targetmatch = pattern.match(escaped)
+            if targetmatch:
+                break
+            blockindex += 1
+            try:
+                escaped += block[blockindex]
+            except IndexError:
+                raise MarkupError('malformed hyperlink target.', lineno)
+        del block[:blockindex]
+        block[0] = (block[0] + ' ')[targetmatch.end()-len(escaped)-1:].strip()
+        target = self.make_target(block, blocktext, lineno,
+                                  targetmatch.group('name'))
+        return [target], blank_finish
+
+    def make_target(self, block, block_text, lineno, target_name):
+        target_type, data = self.parse_target(block, block_text, lineno)
+        if target_type == 'refname':
+            target = nodes.target(block_text, '', refname=normalize_name(data))
+            target.indirect_reference_name = data
+            self.add_target(target_name, '', target, lineno)
+            self.document.note_indirect_target(target)
+            return target
+        elif target_type == 'refuri':
+            target = nodes.target(block_text, '')
+            self.add_target(target_name, data, target, lineno)
+            return target
+        else:
+            return data
+
+    def parse_target(self, block, block_text, lineno):
+        """
+        Determine the type of reference of a target.
+
+        :Return: A 2-tuple, one of:
+
+            - 'refname' and the indirect reference name
+            - 'refuri' and the URI
+            - 'malformed' and a system_message node
+        """
+        if block and block[-1].strip()[-1:] == '_': # possible indirect target
+            reference = ' '.join([line.strip() for line in block])
+            refname = self.is_reference(reference)
+            if refname:
+                return 'refname', refname
+        reference = ''.join([line.strip() for line in block])
+        if reference.find(' ') == -1:
+            return 'refuri', unescape(reference)
+        else:
+            warning = self.reporter.warning(
+                  'Hyperlink target contains whitespace. Perhaps a footnote '
+                  'was intended?',
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return 'malformed', warning
+
+    def is_reference(self, reference):
+        match = self.explicit.patterns.reference.match(
+            whitespace_normalize_name(reference))
+        if not match:
+            return None
+        return unescape(match.group('simple') or match.group('phrase'))
+
+    def add_target(self, targetname, refuri, target, lineno):
+        target.line = lineno
+        if targetname:
+            name = normalize_name(unescape(targetname))
+            target['name'] = name
+            if refuri:
+                uri = self.inliner.adjust_uri(refuri)
+                if uri:
+                    target['refuri'] = uri
+                    self.document.note_external_target(target)
+                else:
+                    raise ApplicationError('problem with URI: %r' % refuri)
+            else:
+                self.document.note_internal_target(target)
+            self.document.note_explicit_target(target, self.parent)
+        else:                       # anonymous target
+            if refuri:
+                target['refuri'] = refuri
+            target['anonymous'] = 1
+            self.document.note_anonymous_target(target)
+
+    def substitution_def(self, match):
+        pattern = self.explicit.patterns.substitution
+        lineno = self.state_machine.abs_line_number()
+        block, indent, offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end(),
+                                                          strip_indent=0)
+        blocktext = (match.string[:match.end()] + '\n'.join(block))
+        block.disconnect()
+        escaped = escape2null(block[0].rstrip())
+        blockindex = 0
+        while 1:
+            subdefmatch = pattern.match(escaped)
+            if subdefmatch:
+                break
+            blockindex += 1
+            try:
+                escaped = escaped + ' ' + escape2null(block[blockindex].strip())
+            except IndexError:
+                raise MarkupError('malformed substitution definition.',
+                                  lineno)
+        del block[:blockindex]          # strip out the substitution marker
+        block[0] = (block[0].strip() + ' ')[subdefmatch.end()-len(escaped)-1:-1]
+        if not block[0]:
+            del block[0]
+            offset += 1
+        while block and not block[-1].strip():
+            block.pop()
+        subname = subdefmatch.group('name')
+        substitution_node = nodes.substitution_definition(blocktext)
+        substitution_node.line = lineno
+        self.document.note_substitution_def(
+            substitution_node,subname, self.parent)
+        if block:
+            block[0] = block[0].strip()
+            new_abs_offset, blank_finish = self.nested_list_parse(
+                  block, input_offset=offset, node=substitution_node,
+                  initial_state='SubstitutionDef', blank_finish=blank_finish)
+            i = 0
+            for node in substitution_node[:]:
+                if not (isinstance(node, nodes.Inline) or
+                        isinstance(node, nodes.Text)):
+                    self.parent += substitution_node[i]
+                    del substitution_node[i]
+                else:
+                    i += 1
+            if len(substitution_node) == 0:
+                msg = self.reporter.warning(
+                      'Substitution definition "%s" empty or invalid.'
+                      % subname,
+                      nodes.literal_block(blocktext, blocktext), line=lineno)
+                return [msg], blank_finish
+            else:
+                return [substitution_node], blank_finish
+        else:
+            msg = self.reporter.warning(
+                  'Substitution definition "%s" missing contents.' % subname,
+                  nodes.literal_block(blocktext, blocktext), line=lineno)
+            return [msg], blank_finish
+
+    def directive(self, match, **option_presets):
+        """Returns a 2-tuple: list of nodes, and a "blank finish" boolean."""
+        type_name = match.group(1)
+        directive_function, messages = directives.directive(
+            type_name, self.memo.language, self.document)
+        self.parent += messages
+        if directive_function:
+            return self.run_directive(
+                directive_function, match, type_name, option_presets)
+        else:
+            return self.unknown_directive(type_name)
+
+    def run_directive(self, directive_fn, match, type_name, option_presets):
+        """
+        Parse a directive then run its directive function.
+
+        Parameters:
+
+        - `directive_fn`: The function implementing the directive.  Uses
+          function attributes ``arguments``, ``options``, and/or ``content``
+          if present.
+
+        - `match`: A regular expression match object which matched the first
+          line of the directive.
+
+        - `type_name`: The directive name, as used in the source text.
+
+        - `option_presets`: A dictionary of preset options, defaults for the
+          directive options.  Currently, only an "alt" option is passed by
+          substitution definitions (value: the substitution name), which may
+          be used by an embedded image directive.
+
+        Returns a 2-tuple: list of nodes, and a "blank finish" boolean.
+        """
+        lineno = self.state_machine.abs_line_number()
+        initial_line_offset = self.state_machine.line_offset
+        indented, indent, line_offset, blank_finish \
+                  = self.state_machine.get_first_known_indented(match.end(),
+                                                                strip_top=0)
+        block_text = '\n'.join(self.state_machine.input_lines[
+            initial_line_offset : self.state_machine.line_offset + 1])
+        try:
+            arguments, options, content, content_offset = (
+                self.parse_directive_block(indented, line_offset,
+                                           directive_fn, option_presets))
+        except MarkupError, detail:
+            error = self.reporter.error(
+                'Error in "%s" directive:\n%s.' % (type_name, detail),
+                nodes.literal_block(block_text, block_text), line=lineno)
+            return [error], blank_finish
+        result = directive_fn(type_name, arguments, options, content, lineno,
+                              content_offset, block_text, self,
+                              self.state_machine)
+        return (result,
+                blank_finish or self.state_machine.is_next_line_blank())
+
+    def parse_directive_block(self, indented, line_offset, directive_fn,
+                              option_presets):
+        arguments = []
+        options = {}
+        argument_spec = getattr(directive_fn, 'arguments', None)
+        if argument_spec and argument_spec[:2] == (0, 0):
+            argument_spec = None
+        option_spec = getattr(directive_fn, 'options', None)
+        content_spec = getattr(directive_fn, 'content', None)
+        if indented and not indented[0].strip():
+            indented.trim_start()
+            line_offset += 1
+        while indented and not indented[-1].strip():
+            indented.trim_end()
+        if indented and (argument_spec or option_spec):
+            for i in range(len(indented)):
+                if not indented[i].strip():
+                    break
+            else:
+                i += 1
+            arg_block = indented[:i]
+            content = indented[i+1:]
+            content_offset = line_offset + i + 1
+        else:
+            content = indented
+            content_offset = line_offset
+            arg_block = []
+        while content and not content[0].strip():
+            content.trim_start()
+            content_offset += 1
+        if option_spec:
+            options, arg_block = self.parse_directive_options(
+                option_presets, option_spec, arg_block)
+            if arg_block and not argument_spec:
+                raise MarkupError('no arguments permitted; blank line '
+                                  'required before content block')
+        if argument_spec:
+            arguments = self.parse_directive_arguments(
+                argument_spec, arg_block)
+        if content and not content_spec:
+            raise MarkupError('no content permitted')
+        return (arguments, options, content, content_offset)
+
+    def parse_directive_options(self, option_presets, option_spec, arg_block):
+        options = option_presets.copy()
+        for i in range(len(arg_block)):
+            if arg_block[i][:1] == ':':
+                opt_block = arg_block[i:]
+                arg_block = arg_block[:i]
+                break
+        else:
+            opt_block = []
+        if opt_block:
+            success, data = self.parse_extension_options(option_spec,
+                                                         opt_block)
+            if success:                 # data is a dict of options
+                options.update(data)
+            else:                       # data is an error string
+                raise MarkupError(data)
+        return options, arg_block
+
+    def parse_directive_arguments(self, argument_spec, arg_block):
+        required, optional, last_whitespace = argument_spec
+        arg_text = '\n'.join(arg_block)
+        arguments = arg_text.split()
+        if len(arguments) < required:
+            raise MarkupError('%s argument(s) required, %s supplied'
+                              % (required, len(arguments)))
+        elif len(arguments) > required + optional:
+            if last_whitespace:
+                arguments = arg_text.split(None, required + optional - 1)
+            else:
+                raise MarkupError(
+                    'maximum %s argument(s) allowed, %s supplied'
+                    % (required + optional, len(arguments)))
+        return arguments
+
+    def parse_extension_options(self, option_spec, datalines):
+        """
+        Parse `datalines` for a field list containing extension options
+        matching `option_spec`.
+
+        :Parameters:
+            - `option_spec`: a mapping of option name to conversion
+              function, which should raise an exception on bad input.
+            - `datalines`: a list of input strings.
+
+        :Return:
+            - Success value, 1 or 0.
+            - An option dictionary on success, an error string on failure.
+        """
+        node = nodes.field_list()
+        newline_offset, blank_finish = self.nested_list_parse(
+              datalines, 0, node, initial_state='ExtensionOptions',
+              blank_finish=1)
+        if newline_offset != len(datalines): # incomplete parse of block
+            return 0, 'invalid option block'
+        try:
+            options = utils.extract_extension_options(node, option_spec)
+        except KeyError, detail:
+            return 0, ('unknown option: "%s"' % detail.args[0])
+        except (ValueError, TypeError), detail:
+            return 0, ('invalid option value: %s' % detail)
+        except utils.ExtensionOptionError, detail:
+            return 0, ('invalid option data: %s' % detail)
+        if blank_finish:
+            return 1, options
+        else:
+            return 0, 'option data incompletely parsed'
+
+    def unknown_directive(self, type_name):
+        lineno = self.state_machine.abs_line_number()
+        indented, indent, offset, blank_finish = \
+              self.state_machine.get_first_known_indented(0, strip_indent=0)
+        text = '\n'.join(indented)
+        error = self.reporter.error(
+              'Unknown directive type "%s".' % type_name,
+              nodes.literal_block(text, text), line=lineno)
+        return [error], blank_finish
+
+    def comment(self, match):
+        if not match.string[match.end():].strip() \
+              and self.state_machine.is_next_line_blank(): # an empty comment?
+            return [nodes.comment()], 1 # "A tiny but practical wart."
+        indented, indent, offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end())
+        while indented and not indented[-1].strip():
+            indented.trim_end()
+        text = '\n'.join(indented)
+        return [nodes.comment(text, text)], blank_finish
+
+    explicit.constructs = [
+          (footnote,
+           re.compile(r"""
+                      \.\.[ ]+          # explicit markup start
+                      \[
+                      (                 # footnote label:
+                          [0-9]+          # manually numbered footnote
+                        |               # *OR*
+                          \#              # anonymous auto-numbered footnote
+                        |               # *OR*
+                          \#%s            # auto-number ed?) footnote label
+                        |               # *OR*
+                          \*              # auto-symbol footnote
+                      )
+                      \]
+                      ([ ]+|$)          # whitespace or end of line
+                      """ % Inliner.simplename, re.VERBOSE | re.UNICODE)),
+          (citation,
+           re.compile(r"""
+                      \.\.[ ]+          # explicit markup start
+                      \[(%s)\]          # citation label
+                      ([ ]+|$)          # whitespace or end of line
+                      """ % Inliner.simplename, re.VERBOSE | re.UNICODE)),
+          (hyperlink_target,
+           re.compile(r"""
+                      \.\.[ ]+          # explicit markup start
+                      _                 # target indicator
+                      (?![ ])           # first char. not space
+                      """, re.VERBOSE)),
+          (substitution_def,
+           re.compile(r"""
+                      \.\.[ ]+          # explicit markup start
+                      \|                # substitution indicator
+                      (?![ ])           # first char. not space
+                      """, re.VERBOSE)),
+          (directive,
+           re.compile(r"""
+                      \.\.[ ]+          # explicit markup start
+                      (%s)              # directive name
+                      [ ]?              # optional space
+                      ::                # directive delimiter
+                      ([ ]+|$)          # whitespace or end of line
+                      """ % Inliner.simplename, re.VERBOSE | re.UNICODE))]
+
+    def explicit_markup(self, match, context, next_state):
+        """Footnotes, hyperlink targets, directives, comments."""
+        nodelist, blank_finish = self.explicit_construct(match)
+        self.parent += nodelist
+        self.explicit_list(blank_finish)
+        return [], next_state, []
+
+    def explicit_construct(self, match):
+        """Determine which explicit construct this is, parse & return it."""
+        errors = []
+        for method, pattern in self.explicit.constructs:
+            expmatch = pattern.match(match.string)
+            if expmatch:
+                try:
+                    return method(self, expmatch)
+                except MarkupError, (message, lineno): # never reached?
+                    errors.append(self.reporter.warning(message, line=lineno))
+                    break
+        nodelist, blank_finish = self.comment(match)
+        return nodelist + errors, blank_finish
+
+    def explicit_list(self, blank_finish):
+        """
+        Create a nested state machine for a series of explicit markup
+        constructs (including anonymous hyperlink targets).
+        """
+        offset = self.state_machine.line_offset + 1   # next line
+        newline_offset, blank_finish = self.nested_list_parse(
+              self.state_machine.input_lines[offset:],
+              input_offset=self.state_machine.abs_line_offset() + 1,
+              node=self.parent, initial_state='Explicit',
+              blank_finish=blank_finish,
+              match_titles=self.state_machine.match_titles)
+        self.goto_line(newline_offset)
+        if not blank_finish:
+            self.parent += self.unindent_warning('Explicit markup')
+
+    def anonymous(self, match, context, next_state):
+        """Anonymous hyperlink targets."""
+        nodelist, blank_finish = self.anonymous_target(match)
+        self.parent += nodelist
+        self.explicit_list(blank_finish)
+        return [], next_state, []
+
+    def anonymous_target(self, match):
+        lineno = self.state_machine.abs_line_number()
+        block, indent, offset, blank_finish \
+              = self.state_machine.get_first_known_indented(match.end(),
+                                                            until_blank=1)
+        blocktext = match.string[:match.end()] + '\n'.join(block)
+        block = [escape2null(line) for line in block]
+        target = self.make_target(block, blocktext, lineno, '')
+        return [target], blank_finish
+
+    def line(self, match, context, next_state):
+        """Section title overline or transition marker."""
+        if self.state_machine.match_titles:
+            return [match.string], 'Line', []
+        elif match.string.strip() == '::':
+            raise statemachine.TransitionCorrection('text')
+        elif len(match.string.strip()) < 4:
+            msg = self.reporter.info(
+                'Unexpected possible title overline or transition.\n'
+                "Treating it as ordinary text because it's so short.",
+                line=self.state_machine.abs_line_number())
+            self.parent += msg
+            raise statemachine.TransitionCorrection('text')
+        else:
+            blocktext = self.state_machine.line
+            msg = self.reporter.severe(
+                  'Unexpected section title or transition.',
+                  nodes.literal_block(blocktext, blocktext),
+                  line=self.state_machine.abs_line_number())
+            self.parent += msg
+            return [], next_state, []
+
+    def text(self, match, context, next_state):
+        """Titles, definition lists, paragraphs."""
+        return [match.string], 'Text', []
+
+
+class RFC2822Body(Body):
+
+    """
+    RFC2822 headers are only valid as the first constructs in documents.  As
+    soon as anything else appears, the `Body` state should take over.
+    """
+
+    patterns = Body.patterns.copy()     # can't modify the original
+    patterns['rfc2822'] = r'[!-9;-~]+:( +|$)'
+    initial_transitions = [(name, 'Body')
+                           for name in Body.initial_transitions]
+    initial_transitions.insert(-1, ('rfc2822', 'Body')) # just before 'text'
+
+    def rfc2822(self, match, context, next_state):
+        """RFC2822-style field list item."""
+        fieldlist = nodes.field_list(CLASS='rfc2822')
+        self.parent += fieldlist
+        field, blank_finish = self.rfc2822_field(match)
+        fieldlist += field
+        offset = self.state_machine.line_offset + 1   # next line
+        newline_offset, blank_finish = self.nested_list_parse(
+              self.state_machine.input_lines[offset:],
+              input_offset=self.state_machine.abs_line_offset() + 1,
+              node=fieldlist, initial_state='RFC2822List',
+              blank_finish=blank_finish)
+        self.goto_line(newline_offset)
+        if not blank_finish:
+            self.parent += self.unindent_warning(
+                  'RFC2822-style field list')
+        return [], next_state, []
+
+    def rfc2822_field(self, match):
+        name = match.string[:match.string.find(':')]
+        indented, indent, line_offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end(),
+                                                          until_blank=1)
+        fieldnode = nodes.field()
+        fieldnode += nodes.field_name(name, name)
+        fieldbody = nodes.field_body('\n'.join(indented))
+        fieldnode += fieldbody
+        if indented:
+            self.nested_parse(indented, input_offset=line_offset,
+                              node=fieldbody)
+        return fieldnode, blank_finish
+
+
+class SpecializedBody(Body):
+
+    """
+    Superclass for second and subsequent compound element members.  Compound
+    elements are lists and list-like constructs.
+
+    All transition methods are disabled (redefined as `invalid_input`).
+    Override individual methods in subclasses to re-enable.
+
+    For example, once an initial bullet list item, say, is recognized, the
+    `BulletList` subclass takes over, with a "bullet_list" node as its
+    container.  Upon encountering the initial bullet list item, `Body.bullet`
+    calls its ``self.nested_list_parse`` (`RSTState.nested_list_parse`), which
+    starts up a nested parsing session with `BulletList` as the initial state.
+    Only the ``bullet`` transition method is enabled in `BulletList`; as long
+    as only bullet list items are encountered, they are parsed and inserted
+    into the container.  The first construct which is *not* a bullet list item
+    triggers the `invalid_input` method, which ends the nested parse and
+    closes the container.  `BulletList` needs to recognize input that is
+    invalid in the context of a bullet list, which means everything *other
+    than* bullet list items, so it inherits the transition list created in
+    `Body`.
+    """
+
+    def invalid_input(self, match=None, context=None, next_state=None):
+        """Not a compound element member. Abort this state machine."""
+        self.state_machine.previous_line() # back up so parent SM can reassess
+        raise EOFError
+
+    indent = invalid_input
+    bullet = invalid_input
+    enumerator = invalid_input
+    field_marker = invalid_input
+    option_marker = invalid_input
+    doctest = invalid_input
+    line_block = invalid_input
+    grid_table_top = invalid_input
+    simple_table_top = invalid_input
+    explicit_markup = invalid_input
+    anonymous = invalid_input
+    line = invalid_input
+    text = invalid_input
+
+
+class BulletList(SpecializedBody):
+
+    """Second and subsequent bullet_list list_items."""
+
+    def bullet(self, match, context, next_state):
+        """Bullet list item."""
+        if match.string[0] != self.parent['bullet']:
+            # different bullet: new list
+            self.invalid_input()
+        listitem, blank_finish = self.list_item(match.end())
+        self.parent += listitem
+        self.blank_finish = blank_finish
+        return [], next_state, []
+
+
+class DefinitionList(SpecializedBody):
+
+    """Second and subsequent definition_list_items."""
+
+    def text(self, match, context, next_state):
+        """Definition lists."""
+        return [match.string], 'Definition', []
+
+
+class EnumeratedList(SpecializedBody):
+
+    """Second and subsequent enumerated_list list_items."""
+
+    def enumerator(self, match, context, next_state):
+        """Enumerated list item."""
+        format, sequence, text, ordinal = self.parse_enumerator(
+              match, self.parent['enumtype'])
+        if (sequence != self.parent['enumtype'] or
+            format != self.format or
+            ordinal != (self.lastordinal + 1) or
+            not self.is_enumerated_list_item(ordinal, sequence, format)):
+            # different enumeration: new list
+            self.invalid_input()
+        listitem, blank_finish = self.list_item(match.end())
+        self.parent += listitem
+        self.blank_finish = blank_finish
+        self.lastordinal = ordinal
+        return [], next_state, []
+
+
+class FieldList(SpecializedBody):
+
+    """Second and subsequent field_list fields."""
+
+    def field_marker(self, match, context, next_state):
+        """Field list field."""
+        field, blank_finish = self.field(match)
+        self.parent += field
+        self.blank_finish = blank_finish
+        return [], next_state, []
+
+
+class OptionList(SpecializedBody):
+
+    """Second and subsequent option_list option_list_items."""
+
+    def option_marker(self, match, context, next_state):
+        """Option list item."""
+        try:
+            option_list_item, blank_finish = self.option_list_item(match)
+        except MarkupError, (message, lineno):
+            self.invalid_input()
+        self.parent += option_list_item
+        self.blank_finish = blank_finish
+        return [], next_state, []
+
+
+class RFC2822List(SpecializedBody, RFC2822Body):
+
+    """Second and subsequent RFC2822-style field_list fields."""
+
+    patterns = RFC2822Body.patterns
+    initial_transitions = RFC2822Body.initial_transitions
+
+    def rfc2822(self, match, context, next_state):
+        """RFC2822-style field list item."""
+        field, blank_finish = self.rfc2822_field(match)
+        self.parent += field
+        self.blank_finish = blank_finish
+        return [], 'RFC2822List', []
+
+    blank = SpecializedBody.invalid_input
+
+
+class ExtensionOptions(FieldList):
+
+    """
+    Parse field_list fields for extension options.
+
+    No nested parsing is done (including inline markup parsing).
+    """
+
+    def parse_field_body(self, indented, offset, node):
+        """Override `Body.parse_field_body` for simpler parsing."""
+        lines = []
+        for line in list(indented) + ['']:
+            if line.strip():
+                lines.append(line)
+            elif lines:
+                text = '\n'.join(lines)
+                node += nodes.paragraph(text, text)
+                lines = []
+
+
+class LineBlock(SpecializedBody):
+
+    """Second and subsequent lines of a line_block."""
+
+    blank = SpecializedBody.invalid_input
+
+    def line_block(self, match, context, next_state):
+        """New line of line block."""
+        lineno = self.state_machine.abs_line_number()
+        line, messages, blank_finish = self.line_block_line(match, lineno)
+        self.parent += line
+        self.parent.parent += messages
+        self.blank_finish = blank_finish
+        return [], next_state, []
+
+
+class Explicit(SpecializedBody):
+
+    """Second and subsequent explicit markup construct."""
+
+    def explicit_markup(self, match, context, next_state):
+        """Footnotes, hyperlink targets, directives, comments."""
+        nodelist, blank_finish = self.explicit_construct(match)
+        self.parent += nodelist
+        self.blank_finish = blank_finish
+        return [], next_state, []
+
+    def anonymous(self, match, context, next_state):
+        """Anonymous hyperlink targets."""
+        nodelist, blank_finish = self.anonymous_target(match)
+        self.parent += nodelist
+        self.blank_finish = blank_finish
+        return [], next_state, []
+
+    blank = SpecializedBody.invalid_input
+
+
+class SubstitutionDef(Body):
+
+    """
+    Parser for the contents of a substitution_definition element.
+    """
+
+    patterns = {
+          'embedded_directive': re.compile(r'(%s)::( +|$)'
+                                           % Inliner.simplename, re.UNICODE),
+          'text': r''}
+    initial_transitions = ['embedded_directive', 'text']
+
+    def embedded_directive(self, match, context, next_state):
+        nodelist, blank_finish = self.directive(match,
+                                                alt=self.parent['name'])
+        self.parent += nodelist
+        if not self.state_machine.at_eof():
+            self.blank_finish = blank_finish
+        raise EOFError
+
+    def text(self, match, context, next_state):
+        if not self.state_machine.at_eof():
+            self.blank_finish = self.state_machine.is_next_line_blank()
+        raise EOFError
+
+
+class Text(RSTState):
+
+    """
+    Classifier of second line of a text block.
+
+    Could be a paragraph, a definition list item, or a title.
+    """
+
+    patterns = {'underline': Body.patterns['line'],
+                'text': r''}
+    initial_transitions = [('underline', 'Body'), ('text', 'Body')]
+
+    def blank(self, match, context, next_state):
+        """End of paragraph."""
+        paragraph, literalnext = self.paragraph(
+              context, self.state_machine.abs_line_number() - 1)
+        self.parent += paragraph
+        if literalnext:
+            self.parent += self.literal_block()
+        return [], 'Body', []
+
+    def eof(self, context):
+        if context:
+            self.blank(None, context, None)
+        return []
+
+    def indent(self, match, context, next_state):
+        """Definition list item."""
+        definitionlist = nodes.definition_list()
+        definitionlistitem, blank_finish = self.definition_list_item(context)
+        definitionlist += definitionlistitem
+        self.parent += definitionlist
+        offset = self.state_machine.line_offset + 1   # next line
+        newline_offset, blank_finish = self.nested_list_parse(
+              self.state_machine.input_lines[offset:],
+              input_offset=self.state_machine.abs_line_offset() + 1,
+              node=definitionlist, initial_state='DefinitionList',
+              blank_finish=blank_finish, blank_finish_state='Definition')
+        self.goto_line(newline_offset)
+        if not blank_finish:
+            self.parent += self.unindent_warning('Definition list')
+        return [], 'Body', []
+
+    def underline(self, match, context, next_state):
+        """Section title."""
+        lineno = self.state_machine.abs_line_number()
+        title = context[0].rstrip()
+        underline = match.string.rstrip()
+        source = title + '\n' + underline
+        messages = []
+        if len(title) > len(underline):
+            if len(underline) < 4:
+                if self.state_machine.match_titles:
+                    msg = self.reporter.info(
+                        'Possible title underline, too short for the title.\n'
+                        "Treating it as ordinary text because it's so short.",
+                        line=lineno)
+                    self.parent += msg
+                raise statemachine.TransitionCorrection('text')
+            else:
+                blocktext = context[0] + '\n' + self.state_machine.line
+                msg = self.reporter.warning(
+                    'Title underline too short.',
+                    nodes.literal_block(blocktext, blocktext), line=lineno)
+                messages.append(msg)
+        if not self.state_machine.match_titles:
+            blocktext = context[0] + '\n' + self.state_machine.line
+            msg = self.reporter.severe(
+                'Unexpected section title.',
+                nodes.literal_block(blocktext, blocktext), line=lineno)
+            self.parent += messages
+            self.parent += msg
+            return [], next_state, []
+        style = underline[0]
+        context[:] = []
+        self.section(title, source, style, lineno - 1, messages)
+        return [], next_state, []
+
+    def text(self, match, context, next_state):
+        """Paragraph."""
+        startline = self.state_machine.abs_line_number() - 1
+        msg = None
+        try:
+            block = self.state_machine.get_text_block(flush_left=1)
+        except statemachine.UnexpectedIndentationError, instance:
+            block, source, lineno = instance.args
+            msg = self.reporter.error('Unexpected indentation.',
+                                      source=source, line=lineno)
+        lines = context + list(block)
+        paragraph, literalnext = self.paragraph(lines, startline)
+        self.parent += paragraph
+        self.parent += msg
+        if literalnext:
+            try:
+                self.state_machine.next_line()
+            except EOFError:
+                pass
+            self.parent += self.literal_block()
+        return [], next_state, []
+
+    def literal_block(self):
+        """Return a list of nodes."""
+        indented, indent, offset, blank_finish = \
+              self.state_machine.get_indented()
+        while indented and not indented[-1].strip():
+            indented.trim_end()
+        if not indented:
+            return self.quoted_literal_block()
+        data = '\n'.join(indented)
+        literal_block = nodes.literal_block(data, data)
+        literal_block.line = offset + 1
+        nodelist = [literal_block]
+        if not blank_finish:
+            nodelist.append(self.unindent_warning('Literal block'))
+        return nodelist
+
+    def quoted_literal_block(self):
+        abs_line_offset = self.state_machine.abs_line_offset()
+        offset = self.state_machine.line_offset
+        parent_node = nodes.Element()
+        new_abs_offset = self.nested_parse(
+            self.state_machine.input_lines[offset:],
+            input_offset=abs_line_offset, node=parent_node, match_titles=0,
+            state_machine_kwargs={'state_classes': (QuotedLiteralBlock,),
+                                  'initial_state': 'QuotedLiteralBlock'})
+        self.goto_line(new_abs_offset)
+        return parent_node.children
+
+    def definition_list_item(self, termline):
+        indented, indent, line_offset, blank_finish = \
+              self.state_machine.get_indented()
+        definitionlistitem = nodes.definition_list_item(
+            '\n'.join(termline + list(indented)))
+        lineno = self.state_machine.abs_line_number() - 1
+        definitionlistitem.line = lineno
+        termlist, messages = self.term(termline, lineno)
+        definitionlistitem += termlist
+        definition = nodes.definition('', *messages)
+        definitionlistitem += definition
+        if termline[0][-2:] == '::':
+            definition += self.reporter.info(
+                  'Blank line missing before literal block (after the "::")? '
+                  'Interpreted as a definition list item.', line=line_offset+1)
+        self.nested_parse(indented, input_offset=line_offset, node=definition)
+        return definitionlistitem, blank_finish
+
+    classifier_delimiter = re.compile(' +: +')
+
+    def term(self, lines, lineno):
+        """Return a definition_list's term and optional classifiers."""
+        assert len(lines) == 1
+        text_nodes, messages = self.inline_text(lines[0], lineno)
+        term_node = nodes.term()
+        node_list = [term_node]
+        for i in range(len(text_nodes)):
+            node = text_nodes[i]
+            if isinstance(node, nodes.Text):
+                parts = self.classifier_delimiter.split(node.rawsource)
+                if len(parts) == 1:
+                    node_list[-1] += node
+                else:
+                    
+                    node_list[-1] += nodes.Text(parts[0].rstrip())
+                    for part in parts[1:]:
+                        classifier_node = nodes.classifier('', part)
+                        node_list.append(classifier_node)
+            else:
+                node_list[-1] += node
+        return node_list, messages
+
+
+class SpecializedText(Text):
+
+    """
+    Superclass for second and subsequent lines of Text-variants.
+
+    All transition methods are disabled. Override individual methods in
+    subclasses to re-enable.
+    """
+
+    def eof(self, context):
+        """Incomplete construct."""
+        return []
+
+    def invalid_input(self, match=None, context=None, next_state=None):
+        """Not a compound element member. Abort this state machine."""
+        raise EOFError
+
+    blank = invalid_input
+    indent = invalid_input
+    underline = invalid_input
+    text = invalid_input
+
+
+class Definition(SpecializedText):
+
+    """Second line of potential definition_list_item."""
+
+    def eof(self, context):
+        """Not a definition."""
+        self.state_machine.previous_line(2) # so parent SM can reassess
+        return []
+
+    def indent(self, match, context, next_state):
+        """Definition list item."""
+        definitionlistitem, blank_finish = self.definition_list_item(context)
+        self.parent += definitionlistitem
+        self.blank_finish = blank_finish
+        return [], 'DefinitionList', []
+
+
+class Line(SpecializedText):
+
+    """
+    Second line of over- & underlined section title or transition marker.
+    """
+
+    eofcheck = 1                        # @@@ ???
+    """Set to 0 while parsing sections, so that we don't catch the EOF."""
+
+    def eof(self, context):
+        """Transition marker at end of section or document."""
+        marker = context[0].strip()
+        if self.memo.section_bubble_up_kludge:
+            self.memo.section_bubble_up_kludge = 0
+        elif len(marker) < 4:
+            self.state_correction(context)
+        if self.eofcheck:               # ignore EOFError with sections
+            lineno = self.state_machine.abs_line_number() - 1
+            transition = nodes.transition(rawsource=context[0])
+            transition.line = lineno
+            self.parent += transition
+        self.eofcheck = 1
+        return []
+
+    def blank(self, match, context, next_state):
+        """Transition marker."""
+        lineno = self.state_machine.abs_line_number() - 1
+        marker = context[0].strip()
+        if len(marker) < 4:
+            self.state_correction(context)
+        transition = nodes.transition(rawsource=marker)
+        transition.line = lineno
+        self.parent += transition
+        return [], 'Body', []
+
+    def text(self, match, context, next_state):
+        """Potential over- & underlined title."""
+        lineno = self.state_machine.abs_line_number() - 1
+        overline = context[0]
+        title = match.string
+        underline = ''
+        try:
+            underline = self.state_machine.next_line()
+        except EOFError:
+            blocktext = overline + '\n' + title
+            if len(overline.rstrip()) < 4:
+                self.short_overline(context, blocktext, lineno, 2)
+            else:
+                msg = self.reporter.severe(
+                    'Incomplete section title.',
+                    nodes.literal_block(blocktext, blocktext), line=lineno)
+                self.parent += msg
+                return [], 'Body', []
+        source = '%s\n%s\n%s' % (overline, title, underline)
+        overline = overline.rstrip()
+        underline = underline.rstrip()
+        if not self.transitions['underline'][0].match(underline):
+            blocktext = overline + '\n' + title + '\n' + underline
+            if len(overline.rstrip()) < 4:
+                self.short_overline(context, blocktext, lineno, 2)
+            else:
+                msg = self.reporter.severe(
+                    'Missing matching underline for section title overline.',
+                    nodes.literal_block(source, source), line=lineno)
+                self.parent += msg
+                return [], 'Body', []
+        elif overline != underline:
+            blocktext = overline + '\n' + title + '\n' + underline
+            if len(overline.rstrip()) < 4:
+                self.short_overline(context, blocktext, lineno, 2)
+            else:
+                msg = self.reporter.severe(
+                      'Title overline & underline mismatch.',
+                      nodes.literal_block(source, source), line=lineno)
+                self.parent += msg
+                return [], 'Body', []
+        title = title.rstrip()
+        messages = []
+        if len(title) > len(overline):
+            blocktext = overline + '\n' + title + '\n' + underline
+            if len(overline.rstrip()) < 4:
+                self.short_overline(context, blocktext, lineno, 2)
+            else:
+                msg = self.reporter.warning(
+                      'Title overline too short.',
+                      nodes.literal_block(source, source), line=lineno)
+                messages.append(msg)
+        style = (overline[0], underline[0])
+        self.eofcheck = 0               # @@@ not sure this is correct
+        self.section(title.lstrip(), source, style, lineno + 1, messages)
+        self.eofcheck = 1
+        return [], 'Body', []
+
+    indent = text                       # indented title
+
+    def underline(self, match, context, next_state):
+        overline = context[0]
+        blocktext = overline + '\n' + self.state_machine.line
+        lineno = self.state_machine.abs_line_number() - 1
+        if len(overline.rstrip()) < 4:
+            self.short_overline(context, blocktext, lineno, 1)
+        msg = self.reporter.error(
+              'Invalid section title or transition marker.',
+              nodes.literal_block(blocktext, blocktext), line=lineno)
+        self.parent += msg
+        return [], 'Body', []
+
+    def short_overline(self, context, blocktext, lineno, lines=1):
+        msg = self.reporter.info(
+            'Possible incomplete section title.\nTreating the overline as '
+            "ordinary text because it's so short.", line=lineno)
+        self.parent += msg
+        self.state_correction(context, lines)
+
+    def state_correction(self, context, lines=1):
+        self.state_machine.previous_line(lines)
+        context[:] = []
+        raise statemachine.StateCorrection('Body', 'text')
+
+
+class QuotedLiteralBlock(RSTState):
+
+    """
+    Nested parse handler for quoted (unindented) literal blocks.
+
+    Special-purpose.  Not for inclusion in `state_classes`.
+    """
+
+    patterns = {'initial_quoted': r'(%(nonalphanum7bit)s)' % Body.pats,
+                'text': r''}
+    initial_transitions = ('initial_quoted', 'text')
+
+    def __init__(self, state_machine, debug=0):
+        RSTState.__init__(self, state_machine, debug)
+        self.messages = []
+        self.initial_lineno = None
+
+    def blank(self, match, context, next_state):
+        if context:
+            raise EOFError
+        else:
+            return context, next_state, []
+
+    def eof(self, context):
+        if context:
+            text = '\n'.join(context)
+            literal_block = nodes.literal_block(text, text)
+            literal_block.line = self.initial_lineno
+            self.parent += literal_block
+        else:
+            self.parent += self.reporter.warning(
+                'Literal block expected; none found.',
+                line=self.state_machine.abs_line_number())
+            self.state_machine.previous_line()
+        self.parent += self.messages
+        return []
+
+    def indent(self, match, context, next_state):
+        assert context, ('QuotedLiteralBlock.indent: context should not '
+                         'be empty!')
+        self.messages.append(
+            self.reporter.error('Unexpected indentation.',
+                                line=self.state_machine.abs_line_number()))
+        self.state_machine.previous_line()
+        raise EOFError
+
+    def initial_quoted(self, match, context, next_state):
+        """Match arbitrary quote character on the first line only."""
+        self.remove_transition('initial_quoted')
+        quote = match.string[0]
+        pattern = re.compile(re.escape(quote))
+        # New transition matches consistent quotes only:
+        self.add_transition('quoted',
+                            (pattern, self.quoted, self.__class__.__name__))
+        self.initial_lineno = self.state_machine.abs_line_number()
+        return [match.string], next_state, []
+
+    def quoted(self, match, context, next_state):
+        """Match consistent quotes on subsequent lines."""
+        context.append(match.string)
+        return context, next_state, []
+
+    def text(self, match, context, next_state):
+        if context:
+            self.messages.append(
+                self.reporter.error('Inconsistent literal block quoting.',
+                                    line=self.state_machine.abs_line_number()))
+            self.state_machine.previous_line()
+        raise EOFError
+
+
+state_classes = (Body, BulletList, DefinitionList, EnumeratedList, FieldList,
+                 OptionList, LineBlock, ExtensionOptions, Explicit, Text,
+                 Definition, Line, SubstitutionDef, RFC2822Body, RFC2822List)
+"""Standard set of State classes used to start `RSTStateMachine`."""

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/tableparser.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/tableparser.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/parsers/rst/tableparser.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,522 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:04 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This module defines table parser classes,which parse plaintext-graphic tables
+and produce a well-formed data structure suitable for building a CALS table.
+
+:Classes:
+    - `GridTableParser`: Parse fully-formed tables represented with a grid.
+    - `SimpleTableParser`: Parse simple tables, delimited by top & bottom
+      borders.
+
+:Exception class: `TableMarkupError`
+
+:Function:
+    `update_dict_of_lists()`: Merge two dictionaries containing list values.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import re
+import sys
+from docutils import DataError
+
+
+class TableMarkupError(DataError): pass
+
+
+class TableParser:
+
+    """
+    Abstract superclass for the common parts of the syntax-specific parsers.
+    """
+
+    head_body_separator_pat = None
+    """Matches the row separator between head rows and body rows."""
+
+    def parse(self, block):
+        """
+        Analyze the text `block` and return a table data structure.
+
+        Given a plaintext-graphic table in `block` (list of lines of text; no
+        whitespace padding), parse the table, construct and return the data
+        necessary to construct a CALS table or equivalent.
+
+        Raise `TableMarkupError` if there is any problem with the markup.
+        """
+        self.setup(block)
+        self.find_head_body_sep()
+        self.parse_table()
+        structure = self.structure_from_cells()
+        return structure
+
+    def find_head_body_sep(self):
+        """Look for a head/body row separator line; store the line index."""
+        for i in range(len(self.block)):
+            line = self.block[i]
+            if self.head_body_separator_pat.match(line):
+                if self.head_body_sep:
+                    raise TableMarkupError(
+                        'Multiple head/body row separators in table (at line '
+                        'offset %s and %s); only one allowed.'
+                        % (self.head_body_sep, i))
+                else:
+                    self.head_body_sep = i
+                    self.block[i] = line.replace('=', '-')
+        if self.head_body_sep == 0 or self.head_body_sep == (len(self.block)
+                                                             - 1):
+            raise TableMarkupError('The head/body row separator may not be '
+                                   'the first or last line of the table.')
+
+
+class GridTableParser(TableParser):
+
+    """
+    Parse a grid table using `parse()`.
+
+    Here's an example of a grid table::
+
+        +------------------------+------------+----------+----------+
+        | Header row, column 1   | Header 2   | Header 3 | Header 4 |
+        +========================+============+==========+==========+
+        | body row 1, column 1   | column 2   | column 3 | column 4 |
+        +------------------------+------------+----------+----------+
+        | body row 2             | Cells may span columns.          |
+        +------------------------+------------+---------------------+
+        | body row 3             | Cells may  | - Table cells       |
+        +------------------------+ span rows. | - contain           |
+        | body row 4             |            | - body elements.    |
+        +------------------------+------------+---------------------+
+
+    Intersections use '+', row separators use '-' (except for one optional
+    head/body row separator, which uses '='), and column separators use '|'.
+
+    Passing the above table to the `parse()` method will result in the
+    following data structure::
+
+        ([24, 12, 10, 10],
+         [[(0, 0, 1, ['Header row, column 1']),
+           (0, 0, 1, ['Header 2']),
+           (0, 0, 1, ['Header 3']),
+           (0, 0, 1, ['Header 4'])]],
+         [[(0, 0, 3, ['body row 1, column 1']),
+           (0, 0, 3, ['column 2']),
+           (0, 0, 3, ['column 3']),
+           (0, 0, 3, ['column 4'])],
+          [(0, 0, 5, ['body row 2']),
+           (0, 2, 5, ['Cells may span columns.']),
+           None,
+           None],
+          [(0, 0, 7, ['body row 3']),
+           (1, 0, 7, ['Cells may', 'span rows.', '']),
+           (1, 1, 7, ['- Table cells', '- contain', '- body elements.']),
+           None],
+          [(0, 0, 9, ['body row 4']), None, None, None]])
+
+    The first item is a list containing column widths (colspecs). The second
+    item is a list of head rows, and the third is a list of body rows. Each
+    row contains a list of cells. Each cell is either None (for a cell unused
+    because of another cell's span), or a tuple. A cell tuple contains four
+    items: the number of extra rows used by the cell in a vertical span
+    (morerows); the number of extra columns used by the cell in a horizontal
+    span (morecols); the line offset of the first line of the cell contents;
+    and the cell contents, a list of lines of text.
+    """
+
+    head_body_separator_pat = re.compile(r'\+=[=+]+=\+ *$')
+
+    def setup(self, block):
+        self.block = block[:]           # make a copy; it may be modified
+        self.block.disconnect()         # don't propagate changes to parent
+        self.bottom = len(block) - 1
+        self.right = len(block[0]) - 1
+        self.head_body_sep = None
+        self.done = [-1] * len(block[0])
+        self.cells = []
+        self.rowseps = {0: [0]}
+        self.colseps = {0: [0]}
+
+    def parse_table(self):
+        """
+        Start with a queue of upper-left corners, containing the upper-left
+        corner of the table itself. Trace out one rectangular cell, remember
+        it, and add its upper-right and lower-left corners to the queue of
+        potential upper-left corners of further cells. Process the queue in
+        top-to-bottom order, keeping track of how much of each text column has
+        been seen.
+
+        We'll end up knowing all the row and column boundaries, cell positions
+        and their dimensions.
+        """
+        corners = [(0, 0)]
+        while corners:
+            top, left = corners.pop(0)
+            if top == self.bottom or left == self.right \
+                  or top <= self.done[left]:
+                continue
+            result = self.scan_cell(top, left)
+            if not result:
+                continue
+            bottom, right, rowseps, colseps = result
+            update_dict_of_lists(self.rowseps, rowseps)
+            update_dict_of_lists(self.colseps, colseps)
+            self.mark_done(top, left, bottom, right)
+            cellblock = self.block.get_2D_block(top + 1, left + 1,
+                                                bottom, right)
+            cellblock.disconnect()      # lines in cell can't sync with parent
+            self.cells.append((top, left, bottom, right, cellblock))
+            corners.extend([(top, right), (bottom, left)])
+            corners.sort()
+        if not self.check_parse_complete():
+            raise TableMarkupError('Malformed table; parse incomplete.')
+
+    def mark_done(self, top, left, bottom, right):
+        """For keeping track of how much of each text column has been seen."""
+        before = top - 1
+        after = bottom - 1
+        for col in range(left, right):
+            assert self.done[col] == before
+            self.done[col] = after
+
+    def check_parse_complete(self):
+        """Each text column should have been completely seen."""
+        last = self.bottom - 1
+        for col in range(self.right):
+            if self.done[col] != last:
+                return None
+        return 1
+
+    def scan_cell(self, top, left):
+        """Starting at the top-left corner, start tracing out a cell."""
+        assert self.block[top][left] == '+'
+        result = self.scan_right(top, left)
+        return result
+
+    def scan_right(self, top, left):
+        """
+        Look for the top-right corner of the cell, and make note of all column
+        boundaries ('+').
+        """
+        colseps = {}
+        line = self.block[top]
+        for i in range(left + 1, self.right + 1):
+            if line[i] == '+':
+                colseps[i] = [top]
+                result = self.scan_down(top, left, i)
+                if result:
+                    bottom, rowseps, newcolseps = result
+                    update_dict_of_lists(colseps, newcolseps)
+                    return bottom, i, rowseps, colseps
+            elif line[i] != '-':
+                return None
+        return None
+
+    def scan_down(self, top, left, right):
+        """
+        Look for the bottom-right corner of the cell, making note of all row
+        boundaries.
+        """
+        rowseps = {}
+        for i in range(top + 1, self.bottom + 1):
+            if self.block[i][right] == '+':
+                rowseps[i] = [right]
+                result = self.scan_left(top, left, i, right)
+                if result:
+                    newrowseps, colseps = result
+                    update_dict_of_lists(rowseps, newrowseps)
+                    return i, rowseps, colseps
+            elif self.block[i][right] != '|':
+                return None
+        return None
+
+    def scan_left(self, top, left, bottom, right):
+        """
+        Noting column boundaries, look for the bottom-left corner of the cell.
+        It must line up with the starting point.
+        """
+        colseps = {}
+        line = self.block[bottom]
+        for i in range(right - 1, left, -1):
+            if line[i] == '+':
+                colseps[i] = [bottom]
+            elif line[i] != '-':
+                return None
+        if line[left] != '+':
+            return None
+        result = self.scan_up(top, left, bottom, right)
+        if result is not None:
+            rowseps = result
+            return rowseps, colseps
+        return None
+
+    def scan_up(self, top, left, bottom, right):
+        """
+        Noting row boundaries, see if we can return to the starting point.
+        """
+        rowseps = {}
+        for i in range(bottom - 1, top, -1):
+            if self.block[i][left] == '+':
+                rowseps[i] = [left]
+            elif self.block[i][left] != '|':
+                return None
+        return rowseps
+
+    def structure_from_cells(self):
+        """
+        From the data collected by `scan_cell()`, convert to the final data
+        structure.
+        """
+        rowseps = self.rowseps.keys()   # list of row boundaries
+        rowseps.sort()
+        rowindex = {}
+        for i in range(len(rowseps)):
+            rowindex[rowseps[i]] = i    # row boundary -> row number mapping
+        colseps = self.colseps.keys()   # list of column boundaries
+        colseps.sort()
+        colindex = {}
+        for i in range(len(colseps)):
+            colindex[colseps[i]] = i    # column boundary -> col number map
+        colspecs = [(colseps[i] - colseps[i - 1] - 1)
+                    for i in range(1, len(colseps))] # list of column widths
+        # prepare an empty table with the correct number of rows & columns
+        onerow = [None for i in range(len(colseps) - 1)]
+        rows = [onerow[:] for i in range(len(rowseps) - 1)]
+        # keep track of # of cells remaining; should reduce to zero
+        remaining = (len(rowseps) - 1) * (len(colseps) - 1)
+        for top, left, bottom, right, block in self.cells:
+            rownum = rowindex[top]
+            colnum = colindex[left]
+            assert rows[rownum][colnum] is None, (
+                  'Cell (row %s, column %s) already used.'
+                  % (rownum + 1, colnum + 1))
+            morerows = rowindex[bottom] - rownum - 1
+            morecols = colindex[right] - colnum - 1
+            remaining -= (morerows + 1) * (morecols + 1)
+            # write the cell into the table
+            rows[rownum][colnum] = (morerows, morecols, top + 1, block)
+        assert remaining == 0, 'Unused cells remaining.'
+        if self.head_body_sep:          # separate head rows from body rows
+            numheadrows = rowindex[self.head_body_sep]
+            headrows = rows[:numheadrows]
+            bodyrows = rows[numheadrows:]
+        else:
+            headrows = []
+            bodyrows = rows
+        return (colspecs, headrows, bodyrows)
+
+
+class SimpleTableParser(TableParser):
+
+    """
+    Parse a simple table using `parse()`.
+
+    Here's an example of a simple table::
+
+        =====  =====
+        col 1  col 2
+        =====  =====
+        1      Second column of row 1.
+        2      Second column of row 2.
+               Second line of paragraph.
+        3      - Second column of row 3.
+
+               - Second item in bullet
+                 list (row 3, column 2).
+        4 is a span
+        ------------
+        5
+        =====  =====
+
+    Top and bottom borders use '=', column span underlines use '-', column
+    separation is indicated with spaces.
+
+    Passing the above table to the `parse()` method will result in the
+    following data structure, whose interpretation is the same as for
+    `GridTableParser`::
+
+        ([5, 25],
+         [[(0, 0, 1, ['col 1']),
+           (0, 0, 1, ['col 2'])]],
+         [[(0, 0, 3, ['1']),
+           (0, 0, 3, ['Second column of row 1.'])],
+          [(0, 0, 4, ['2']),
+           (0, 0, 4, ['Second column of row 2.',
+                      'Second line of paragraph.'])],
+          [(0, 0, 6, ['3']),
+           (0, 0, 6, ['- Second column of row 3.',
+                      '',
+                      '- Second item in bullet',
+                      '  list (row 3, column 2).'])],
+          [(0, 1, 10, ['4 is a span'])],
+          [(0, 0, 12, ['5']),
+           (0, 0, 12, [''])]])
+    """
+
+    head_body_separator_pat = re.compile('=[ =]*$')
+    span_pat = re.compile('-[ -]*$')
+
+    def setup(self, block):
+        self.block = block[:]           # make a copy; it will be modified
+        self.block.disconnect()         # don't propagate changes to parent
+        # Convert top & bottom borders to column span underlines:
+        self.block[0] = self.block[0].replace('=', '-')
+        self.block[-1] = self.block[-1].replace('=', '-')
+        self.head_body_sep = None
+        self.columns = []
+        self.border_end = None
+        self.table = []
+        self.done = [-1] * len(block[0])
+        self.rowseps = {0: [0]}
+        self.colseps = {0: [0]}
+
+    def parse_table(self):
+        """
+        First determine the column boundaries from the top border, then
+        process rows.  Each row may consist of multiple lines; accumulate
+        lines until a row is complete.  Call `self.parse_row` to finish the
+        job.
+        """
+        # Top border must fully describe all table columns.
+        self.columns = self.parse_columns(self.block[0], 0)
+        self.border_end = self.columns[-1][1]
+        firststart, firstend = self.columns[0]
+        offset = 1                      # skip top border
+        start = 1
+        text_found = None
+        while offset < len(self.block):
+            line = self.block[offset]
+            if self.span_pat.match(line):
+                # Column span underline or border; row is complete.
+                self.parse_row(self.block[start:offset], start,
+                               (line.rstrip(), offset))
+                start = offset + 1
+                text_found = None
+            elif line[firststart:firstend].strip():
+                # First column not blank, therefore it's a new row.
+                if text_found and offset != start:
+                    self.parse_row(self.block[start:offset], start)
+                start = offset
+                text_found = 1
+            elif not text_found:
+                start = offset + 1
+            offset += 1
+
+    def parse_columns(self, line, offset):
+        """
+        Given a column span underline, return a list of (begin, end) pairs.
+        """
+        cols = []
+        end = 0
+        while 1:
+            begin = line.find('-', end)
+            end = line.find(' ', begin)
+            if begin < 0:
+                break
+            if end < 0:
+                end = len(line)
+            cols.append((begin, end))
+        if self.columns:
+            if cols[-1][1] != self.border_end:
+                raise TableMarkupError('Column span incomplete at line '
+                                       'offset %s.' % offset)
+            # Allow for an unbounded rightmost column:
+            cols[-1] = (cols[-1][0], self.columns[-1][1])
+        return cols
+
+    def init_row(self, colspec, offset):
+        i = 0
+        cells = []
+        for start, end in colspec:
+            morecols = 0
+            try:
+                assert start == self.columns[i][0]
+                while end != self.columns[i][1]:
+                    i += 1
+                    morecols += 1
+            except (AssertionError, IndexError):
+                raise TableMarkupError('Column span alignment problem at '
+                                       'line offset %s.' % (offset + 1))
+            cells.append([0, morecols, offset, []])
+            i += 1
+        return cells
+
+    def parse_row(self, lines, start, spanline=None):
+        """
+        Given the text `lines` of a row, parse it and append to `self.table`.
+
+        The row is parsed according to the current column spec (either
+        `spanline` if provided or `self.columns`).  For each column, extract
+        text from each line, and check for text in column margins.  Finally,
+        adjust for insigificant whitespace.
+        """
+        if not (lines or spanline):
+            # No new row, just blank lines.
+            return
+        if spanline:
+            columns = self.parse_columns(*spanline)
+            span_offset = spanline[1]
+        else:
+            columns = self.columns[:]
+            span_offset = start
+        self.check_columns(lines, start, columns)
+        row = self.init_row(columns, start)
+        for i in range(len(columns)):
+            start, end = columns[i]
+            cellblock = lines.get_2D_block(0, start, len(lines), end)
+            cellblock.disconnect()      # lines in cell can't sync with parent
+            row[i][3] = cellblock
+        self.table.append(row)
+
+    def check_columns(self, lines, first_line, columns):
+        """
+        Check for text in column margins and text overflow in the last column.
+        Raise TableMarkupError if anything but whitespace is in column margins.
+        Adjust the end value for the last column if there is text overflow.
+        """
+        # "Infinite" value for a dummy last column's beginning, used to
+        # check for text overflow:
+        columns.append((sys.maxint, None))
+        lastcol = len(columns) - 2
+        for i in range(len(columns) - 1):
+            start, end = columns[i]
+            nextstart = columns[i+1][0]
+            offset = 0
+            for line in lines:
+                if i == lastcol and line[end:].strip():
+                    text = line[start:].rstrip()
+                    new_end = start + len(text)
+                    columns[i] = (start, new_end)
+                    main_start, main_end = self.columns[-1]
+                    if new_end > main_end:
+                        self.columns[-1] = (main_start, new_end)
+                elif line[end:nextstart].strip():
+                    raise TableMarkupError('Text in column margin at line '
+                                           'offset %s.' % (first_line + offset))
+                offset += 1
+        columns.pop()
+
+    def structure_from_cells(self):
+        colspecs = [end - start for start, end in self.columns]
+        first_body_row = 0
+        if self.head_body_sep:
+            for i in range(len(self.table)):
+                if self.table[i][0][2] > self.head_body_sep:
+                    first_body_row = i
+                    break
+        return (colspecs, self.table[:first_body_row],
+                self.table[first_body_row:])
+
+
+def update_dict_of_lists(master, newdata):
+    """
+    Extend the list values of `master` with those from `newdata`.
+
+    Both parameters must be dictionaries containing list values.
+    """
+    for key, values in newdata.items():
+        master.setdefault(key, []).extend(values)

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,89 @@
+# Authors: David Goodger; Ueli Schlaepfer
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:05 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This package contains Docutils Reader modules.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+from docutils import utils, parsers, Component
+from docutils.transforms import universal
+
+
+class Reader(Component):
+
+    """
+    Abstract base class for docutils Readers.
+
+    Each reader module or package must export a subclass also called 'Reader'.
+
+    The three steps of a Reader's responsibility are defined: `scan()`,
+    `parse()`, and `transform()`. Call `read()` to process a document.
+    """
+
+    component_type = 'reader'
+    config_section = 'readers'
+
+    def __init__(self, parser=None, parser_name='restructuredtext'):
+        """
+        Initialize the Reader instance.
+
+        Several instance attributes are defined with dummy initial values.
+        Subclasses may use these attributes as they wish.
+        """
+
+        self.parser = parser
+        """A `parsers.Parser` instance shared by all doctrees.  May be left
+        unspecified if the document source determines the parser."""
+
+        if parser is None and parser_name:
+            self.set_parser(parser_name)
+
+        self.source = None
+        """`docutils.io` IO object, source of input data."""
+
+        self.input = None
+        """Raw text input; either a single string or, for more complex cases,
+        a collection of strings."""
+
+    def set_parser(self, parser_name):
+        """Set `self.parser` by name."""
+        parser_class = parsers.get_parser_class(parser_name)
+        self.parser = parser_class()
+
+    def read(self, source, parser, settings):
+        self.source = source
+        if not self.parser:
+            self.parser = parser
+        self.settings = settings
+        self.input = self.source.read()
+        self.parse()
+        return self.document
+
+    def parse(self):
+        """Parse `self.input` into a document tree."""
+        self.document = document = self.new_document()
+        self.parser.parse(self.input, document)
+        document.current_source = document.current_line = None
+
+    def new_document(self):
+        """Create and return a new empty document tree (root node)."""
+        document = utils.new_document(self.source.source_path, self.settings)
+        return document
+
+
+_reader_aliases = {}
+
+def get_reader_class(reader_name):
+    """Return the Reader class from the `reader_name` module."""
+    reader_name = reader_name.lower()
+    if _reader_aliases.has_key(reader_name):
+        reader_name = _reader_aliases[reader_name]
+    module = __import__(reader_name, globals(), locals())
+    return module.Reader

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/pep.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/pep.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/pep.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,52 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:05 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Python Enhancement Proposal (PEP) Reader.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+from docutils.readers import standalone
+from docutils.transforms import peps, references
+from docutils.parsers import rst
+
+
+class Reader(standalone.Reader):
+
+    supported = ('pep',)
+    """Contexts this reader supports."""
+
+    settings_spec = (
+        'PEP Reader Option Defaults',
+        'The --pep-references and --rfc-references options (for the '
+        'reStructuredText parser) are on by default.',
+        ())
+
+    config_section = 'pep reader'
+    config_section_dependencies = ('readers', 'standalone reader')
+
+    default_transforms = (references.Substitutions,
+                          peps.Headers,
+                          peps.Contents,
+                          references.ChainedTargets,
+                          references.AnonymousHyperlinks,
+                          references.IndirectHyperlinks,
+                          peps.TargetNotes,
+                          references.Footnotes,
+                          references.ExternalTargets,
+                          references.InternalTargets,)
+
+    settings_default_overrides = {'pep_references': 1, 'rfc_references': 1}
+
+    inliner_class = rst.states.Inliner
+
+    def __init__(self, parser=None, parser_name=None):
+        """`parser` should be ``None``."""
+        if parser is None:
+            parser = rst.Parser(rfc2822=1, inliner=self.inliner_class())
+        standalone.Reader.__init__(self, parser, '')

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,132 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.3.2.5 $
+# Date: $Date: 2005/01/07 13:26:05 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This package contains the Python Source Reader modules.
+
+It requires Python 2.2 or higher (`moduleparser` depends on the
+`compiler` and `tokenize` modules).
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+import docutils.readers
+from docutils.readers.python import moduleparser
+from docutils import parsers
+from docutils import nodes
+from docutils.readers.python import pynodes
+from docutils import readers
+
+class Reader(docutils.readers.Reader):
+
+    config_section = 'python reader'
+    config_section_dependencies = ('readers',)
+
+    default_parser = 'restructuredtext'
+
+    def parse(self):
+        """Parse `self.input` into a document tree."""
+        self.document = document = self.new_document()
+        module_section = moduleparser.parse_module(self.input,
+                                                   self.source.source_path)
+        module_section.walk(DocformatVisitor(self.document))
+        visitor = DocstringFormattingVisitor(
+            document=document,
+            default_parser=self.default_parser)
+        module_section.walk(visitor)
+        self.document.append(module_section)
+
+
+class DocformatVisitor(nodes.SparseNodeVisitor):
+
+    """
+    This sets docformat attributes in a module.  Wherever an assignment
+    to __docformat__ is found, we look for the enclosing scope -- a class,
+    a module, or a function -- and set the docformat attribute there.
+
+    We can't do this during the DocstringFormattingVisitor walking,
+    because __docformat__ may appear below a docstring in that format
+    (typically below the module docstring).
+    """
+
+    def visit_attribute(self, node):
+        assert isinstance(node[0], pynodes.object_name)
+        name = node[0][0].data
+        if name != '__docformat__':
+            return
+        value = None
+        for child in children:
+            if isinstance(child, pynodes.expression_value):
+                value = child[0].data
+                break
+        assert value.startswith("'") or value.startswith('"'), "__docformat__ must be assigned a string literal (not %s); line: %s" % (value, node['lineno'])
+        name = name[1:-1]
+        looking_in = node.parent
+        while not isinstance(looking_in, (pynodes.module_section,
+                                          pynodes.function_section,
+                                          pynodes.class_section)):
+            looking_in = looking_in.parent
+        looking_in['docformat'] = name
+
+
+class DocstringFormattingVisitor(nodes.SparseNodeVisitor):
+
+    def __init__(self, document, default_parser):
+        self.document = document
+        self.default_parser = default_parser
+        self.parsers = {}
+
+    def visit_docstring(self, node):
+        text = node[0].data
+        docformat = self.find_docformat(node)
+        del node[0]
+        node['docformat'] = docformat
+        parser = self.get_parser(docformat)
+        parser.parse(text, self.document)
+        for child in self.document.get_children():
+            node.append(child)
+        self.document.current_source = self.document.current_line = None
+        del self.document[:]
+
+    def get_parser(self, parser_name):
+        """
+        Get a parser based on its name.  We reuse parsers during this
+        visitation, so parser instances are cached.
+        """
+        parser_name = parsers._parser_aliases.get(parser_name, parser_name)
+        if not self.parsers.has_key(parser_name):
+            cls = parsers.get_parser_class(parser_name)
+            self.parsers[parser_name] = cls()
+        return self.parsers[parser_name]
+
+    def find_docformat(self, node):
+        """
+        Find the __docformat__ closest to this node (i.e., look in the
+        class or module)
+        """
+        while node:
+            if node.get('docformat'):
+                return node['docformat']
+            node = node.parent
+        return self.default_parser
+
+
+if __name__ == '__main__':
+    try:
+        import locale
+        locale.setlocale(locale.LC_ALL, '')
+    except:
+        pass
+
+    from docutils.core import publish_cmdline, default_description
+
+    description = ('Generates pseudo-XML from Python modules '
+                   '(for testing purposes).  ' + default_description)
+
+    publish_cmdline(description=description,
+                    reader=Reader())

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/moduleparser.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/moduleparser.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/moduleparser.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,758 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.3.2.5 $
+# Date: $Date: 2005/01/07 13:26:05 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Parser for Python modules.  Requires Python 2.2 or higher.
+
+The `parse_module()` function takes a module's text and file name,
+runs it through the module parser (using compiler.py and tokenize.py)
+and produces a parse tree of the source code, using the nodes as found
+in pynodes.py.  For example, given this module (x.py)::
+
+    # comment
+
+    '''Docstring'''
+
+    '''Additional docstring'''
+
+    __docformat__ = 'reStructuredText'
+
+    a = 1
+    '''Attribute docstring'''
+
+    class C(Super):
+
+        '''C's docstring'''
+
+        class_attribute = 1
+        '''class_attribute's docstring'''
+
+        def __init__(self, text=None):
+            '''__init__'s docstring'''
+
+            self.instance_attribute = (text * 7
+                                       + ' whaddyaknow')
+            '''instance_attribute's docstring'''
+
+
+    def f(x,                            # parameter x
+          y=a*5,                        # parameter y
+          *args):                       # parameter args
+        '''f's docstring'''
+        return [x + item for item in args]
+
+    f.function_attribute = 1
+    '''f.function_attribute's docstring'''
+
+The module parser will produce this module documentation tree::
+
+    <module_section filename="test data">
+        <docstring>
+            Docstring
+        <docstring lineno="5">
+            Additional docstring
+        <attribute lineno="7">
+	    <object_name>
+	        __docformat__
+            <expression_value lineno="7">
+                'reStructuredText'
+        <attribute lineno="9">
+	    <object_name>
+	        a
+            <expression_value lineno="9">
+                1
+            <docstring lineno="10">
+                Attribute docstring
+        <class_section lineno="12">
+	    <object_name>
+	        C
+            <class_base>
+	        Super
+            <docstring lineno="12">
+                C's docstring
+            <attribute lineno="16">
+	        <object_name>
+		    class_attribute
+                <expression_value lineno="16">
+                    1
+                <docstring lineno="17">
+                    class_attribute's docstring
+            <method_section lineno="19">
+	        <object_name>
+		    __init__
+                <docstring lineno="19">
+                    __init__'s docstring
+                <parameter_list lineno="19">
+                    <parameter lineno="19">
+		        <object_name>
+			    self
+                    <parameter lineno="19">
+		        <object_name>
+			    text
+                        <parameter_default lineno="19">
+                            None
+                <attribute lineno="22">
+		    <object_name>
+		        self.instance_attribute
+                    <expression_value lineno="22">
+                        (text * 7 + ' whaddyaknow')
+                    <docstring lineno="24">
+                        instance_attribute's docstring
+        <function_section lineno="27">
+	    <object_name>
+	        f
+            <docstring lineno="27">
+                f's docstring
+            <parameter_list lineno="27">
+                <parameter lineno="27">
+		    <object_name>
+		        x
+                    <comment>
+                        # parameter x
+                <parameter lineno="27">
+		    <object_name>
+		        y
+                    <parameter_default lineno="27">
+                        a * 5
+                    <comment>
+                        # parameter y
+                <parameter excess_positional="1" lineno="27">
+		    <object_name>
+		        args
+                    <comment>
+                        # parameter args
+        <attribute lineno="33">
+	    <object_name>
+	        f.function_attribute
+            <expression_value lineno="33">
+                1
+            <docstring lineno="34">
+                f.function_attribute's docstring
+
+(Comments are not implemented yet.)
+
+compiler.parse() provides most of what's needed for this doctree, and
+"tokenize" can be used to get the rest.  We can determine the line
+number from the compiler.parse() AST, and the TokenParser.rhs(lineno)
+method provides the rest.
+
+The Docutils Python reader component will transform this module doctree into a
+Python-specific Docutils doctree, and then a `stylist transform`_ will
+further transform it into a generic doctree.  Namespaces will have to be
+compiled for each of the scopes, but I'm not certain at what stage of
+processing.
+
+It's very important to keep all docstring processing out of this, so that it's
+a completely generic and not tool-specific.
+
+> Why perform all of those transformations?  Why not go from the AST to a
+> generic doctree?  Or, even from the AST to the final output?
+
+I want the docutils.readers.python.moduleparser.parse_module() function to
+produce a standard documentation-oriented tree that can be used by any tool.
+We can develop it together without having to compromise on the rest of our
+design (i.e., HappyDoc doesn't have to be made to work like Docutils, and
+vice-versa).  It would be a higher-level version of what compiler.py provides.
+
+The Python reader component transforms this generic AST into a Python-specific
+doctree (it knows about modules, classes, functions, etc.), but this is
+specific to Docutils and cannot be used by HappyDoc or others.  The stylist
+transform does the final layout, converting Python-specific structures
+("class" sections, etc.) into a generic doctree using primitives (tables,
+sections, lists, etc.).  This generic doctree does *not* know about Python
+structures any more.  The advantage is that this doctree can be handed off to
+any of the output writers to create any output format we like.
+
+The latter two transforms are separate because I want to be able to have
+multiple independent layout styles (multiple runtime-selectable "stylist
+transforms").  Each of the existing tools (HappyDoc, pydoc, epydoc, Crystal,
+etc.) has its own fixed format.  I personally don't like the tables-based
+format produced by these tools, and I'd like to be able to customize the
+format easily.  That's the goal of stylist transforms, which are independent
+from the Reader component itself.  One stylist transform could produce
+HappyDoc-like output, another could produce output similar to module docs in
+the Python library reference manual, and so on.
+
+It's for exactly this reason:
+
+>> It's very important to keep all docstring processing out of this, so that
+>> it's a completely generic and not tool-specific.
+
+... but it goes past docstring processing.  It's also important to keep style
+decisions and tool-specific data transforms out of this module parser.
+
+
+Issues
+======
+
+* At what point should namespaces be computed?  Should they be part of the
+  basic AST produced by the ASTVisitor walk, or generated by another tree
+  traversal?
+
+* At what point should a distinction be made between local variables &
+  instance attributes in __init__ methods?
+
+* Docstrings are getting their lineno from their parents.  Should the
+  TokenParser find the real line no's?
+
+* Comments: include them?  How and when?  Only full-line comments, or
+  parameter comments too?  (See function "f" above for an example.)
+
+* Module could use more docstrings & refactoring in places.
+
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import compiler
+import compiler.ast
+import tokenize
+import token
+from compiler.consts import OP_ASSIGN
+from compiler.visitor import ASTVisitor
+from types import StringType, UnicodeType, TupleType
+from docutils.readers.python import pynodes
+from docutils.nodes import Text
+
+
+def parse_module(module_text, filename):
+    """Return a module documentation tree from `module_text`."""
+    ast = compiler.parse(module_text)
+    token_parser = TokenParser(module_text)
+    visitor = ModuleVisitor(filename, token_parser)
+    compiler.walk(ast, visitor, walker=visitor)
+    return visitor.module
+
+class BaseVisitor(ASTVisitor):
+
+    def __init__(self, token_parser):
+        ASTVisitor.__init__(self)
+        self.token_parser = token_parser
+        self.context = []
+        self.documentable = None
+
+    def default(self, node, *args):
+        self.documentable = None
+        #print 'in default (%s)' % node.__class__.__name__
+        #ASTVisitor.default(self, node, *args)
+
+    def default_visit(self, node, *args):
+        #print 'in default_visit (%s)' % node.__class__.__name__
+        ASTVisitor.default(self, node, *args)
+
+
+class DocstringVisitor(BaseVisitor):
+
+    def visitDiscard(self, node):
+        if self.documentable:
+            self.visit(node.expr)
+
+    def visitConst(self, node):
+        if self.documentable:
+            if type(node.value) in (StringType, UnicodeType):
+                self.documentable.append(make_docstring(node.value, node.lineno))
+            else:
+                self.documentable = None
+
+    def visitStmt(self, node):
+        self.default_visit(node)
+
+
+class AssignmentVisitor(DocstringVisitor):
+
+    def visitAssign(self, node):
+        visitor = AttributeVisitor(self.token_parser)
+        compiler.walk(node, visitor, walker=visitor)
+        if visitor.attributes:
+            self.context[-1].extend(visitor.attributes)
+        if len(visitor.attributes) == 1:
+            self.documentable = visitor.attributes[0]
+        else:
+            self.documentable = None
+
+
+class ModuleVisitor(AssignmentVisitor):
+
+    def __init__(self, filename, token_parser):
+        AssignmentVisitor.__init__(self, token_parser)
+        self.filename = filename
+        self.module = None
+
+    def visitModule(self, node):
+        self.module = module = pynodes.module_section()
+        module['filename'] = self.filename
+        append_docstring(module, node.doc, node.lineno)
+        self.context.append(module)
+        self.documentable = module
+        self.visit(node.node)
+        self.context.pop()
+
+    def visitImport(self, node):
+        self.context[-1] += make_import_group(names=node.names,
+                                              lineno=node.lineno)
+        self.documentable = None
+
+    def visitFrom(self, node):
+        self.context[-1].append(
+            make_import_group(names=node.names, from_name=node.modname,
+                              lineno=node.lineno))
+        self.documentable = None
+
+    def visitFunction(self, node):
+        visitor = FunctionVisitor(self.token_parser,
+                                  function_class=pynodes.function_section)
+        compiler.walk(node, visitor, walker=visitor)
+        self.context[-1].append(visitor.function)
+
+    def visitClass(self, node):
+        visitor = ClassVisitor(self.token_parser)
+        compiler.walk(node, visitor, walker=visitor)
+        self.context[-1].append(visitor.klass)
+
+
+class AttributeVisitor(BaseVisitor):
+
+    def __init__(self, token_parser):
+        BaseVisitor.__init__(self, token_parser)
+        self.attributes = pynodes.class_attribute_section()
+
+    def visitAssign(self, node):
+        # Don't visit the expression itself, just the attribute nodes:
+        for child in node.nodes:
+            self.dispatch(child)
+        expression_text = self.token_parser.rhs(node.lineno)
+        expression = pynodes.expression_value()
+        expression.append(Text(expression_text))
+        for attribute in self.attributes:
+            attribute.append(expression)
+
+    def visitAssName(self, node):
+        self.attributes.append(make_attribute(node.name,
+                                              lineno=node.lineno))
+
+    def visitAssTuple(self, node):
+        attributes = self.attributes
+        self.attributes = []
+        self.default_visit(node)
+        n = pynodes.attribute_tuple()
+        n.extend(self.attributes)
+        n['lineno'] = self.attributes[0]['lineno']
+        attributes.append(n)
+        self.attributes = attributes
+        #self.attributes.append(att_tuple)
+
+    def visitAssAttr(self, node):
+        self.default_visit(node, node.attrname)
+
+    def visitGetattr(self, node, suffix):
+        self.default_visit(node, node.attrname + '.' + suffix)
+
+    def visitName(self, node, suffix):
+        self.attributes.append(make_attribute(node.name + '.' + suffix,
+                                              lineno=node.lineno))
+
+
+class FunctionVisitor(DocstringVisitor):
+
+    in_function = 0
+
+    def __init__(self, token_parser, function_class):
+        DocstringVisitor.__init__(self, token_parser)
+        self.function_class = function_class
+
+    def visitFunction(self, node):
+        if self.in_function:
+            self.documentable = None
+            # Don't bother with nested function definitions.
+            return
+        self.in_function = 1
+        self.function = function = make_function_like_section(
+            name=node.name,
+            lineno=node.lineno,
+            doc=node.doc,
+            function_class=self.function_class)
+        self.context.append(function)
+        self.documentable = function
+        self.parse_parameter_list(node)
+        self.visit(node.code)
+        self.context.pop()
+
+    def parse_parameter_list(self, node):
+        parameters = []
+        special = []
+        argnames = list(node.argnames)
+        if node.kwargs:
+            special.append(make_parameter(argnames[-1], excess_keyword=1))
+            argnames.pop()
+        if node.varargs:
+            special.append(make_parameter(argnames[-1],
+                                          excess_positional=1))
+            argnames.pop()
+        defaults = list(node.defaults)
+        defaults = [None] * (len(argnames) - len(defaults)) + defaults
+        function_parameters = self.token_parser.function_parameters(
+            node.lineno)
+        #print >>sys.stderr, function_parameters
+        for argname, default in zip(argnames, defaults):
+            if type(argname) is TupleType:
+                parameter = pynodes.parameter_tuple()
+                for tuplearg in argname:
+                    parameter.append(make_parameter(tuplearg))
+                argname = normalize_parameter_name(argname)
+            else:
+                parameter = make_parameter(argname)
+            if default:
+                n_default = pynodes.parameter_default()
+                n_default.append(Text(function_parameters[argname]))
+                parameter.append(n_default)
+            parameters.append(parameter)
+        if parameters or special:
+            special.reverse()
+            parameters.extend(special)
+            parameter_list = pynodes.parameter_list()
+            parameter_list.extend(parameters)
+            self.function.append(parameter_list)
+
+
+class ClassVisitor(AssignmentVisitor):
+
+    in_class = 0
+
+    def __init__(self, token_parser):
+        AssignmentVisitor.__init__(self, token_parser)
+        self.bases = []
+
+    def visitClass(self, node):
+        if self.in_class:
+            self.documentable = None
+            # Don't bother with nested class definitions.
+            return
+        self.in_class = 1
+        #import mypdb as pdb
+        #pdb.set_trace()
+        for base in node.bases:
+            self.visit(base)
+        self.klass = klass = make_class_section(node.name, self.bases,
+                                                doc=node.doc,
+                                                lineno=node.lineno)
+        self.context.append(klass)
+        self.documentable = klass
+        self.visit(node.code)
+        self.context.pop()
+
+    def visitGetattr(self, node, suffix=None):
+        if suffix:
+            name = node.attrname + '.' + suffix
+        else:
+            name = node.attrname
+        self.default_visit(node, name)
+
+    def visitName(self, node, suffix=None):
+        if suffix:
+            name = node.name + '.' + suffix
+        else:
+            name = node.name
+        self.bases.append(name)
+
+    def visitFunction(self, node):
+        if node.name == '__init__':
+            visitor = InitMethodVisitor(self.token_parser,
+                                        function_class=pynodes.method_section)
+            compiler.walk(node, visitor, walker=visitor)
+        else:
+            visitor = FunctionVisitor(self.token_parser,
+                                      function_class=pynodes.method_section)
+            compiler.walk(node, visitor, walker=visitor)
+        self.context[-1].append(visitor.function)
+
+
+class InitMethodVisitor(FunctionVisitor, AssignmentVisitor): pass
+
+
+class TokenParser:
+
+    def __init__(self, text):
+        self.text = text + '\n\n'
+        self.lines = self.text.splitlines(1)
+        self.generator = tokenize.generate_tokens(iter(self.lines).next)
+        self.next()
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        self.token = self.generator.next()
+        self.type, self.string, self.start, self.end, self.line = self.token
+        return self.token
+
+    def goto_line(self, lineno):
+        while self.start[0] < lineno:
+            self.next()
+        return token
+
+    def rhs(self, lineno):
+        """
+        Return a whitespace-normalized expression string from the right-hand
+        side of an assignment at line `lineno`.
+        """
+        self.goto_line(lineno)
+        while self.string != '=':
+            self.next()
+        self.stack = None
+        while self.type != token.NEWLINE and self.string != ';':
+            if self.string == '=' and not self.stack:
+                self.tokens = []
+                self.stack = []
+                self._type = None
+                self._string = None
+                self._backquote = 0
+            else:
+                self.note_token()
+            self.next()
+        self.next()
+        text = ''.join(self.tokens)
+        return text.strip()
+
+    closers = {')': '(', ']': '[', '}': '{'}
+    openers = {'(': 1, '[': 1, '{': 1}
+    del_ws_prefix = {'.': 1, '=': 1, ')': 1, ']': 1, '}': 1, ':': 1, ',': 1}
+    no_ws_suffix = {'.': 1, '=': 1, '(': 1, '[': 1, '{': 1}
+
+    def note_token(self):
+        if self.type == tokenize.NL:
+            return
+        del_ws = self.del_ws_prefix.has_key(self.string)
+        append_ws = not self.no_ws_suffix.has_key(self.string)
+        if self.openers.has_key(self.string):
+            self.stack.append(self.string)
+            if (self._type == token.NAME
+                or self.closers.has_key(self._string)):
+                del_ws = 1
+        elif self.closers.has_key(self.string):
+            assert self.stack[-1] == self.closers[self.string]
+            self.stack.pop()
+        elif self.string == '`':
+            if self._backquote:
+                del_ws = 1
+                assert self.stack[-1] == '`'
+                self.stack.pop()
+            else:
+                append_ws = 0
+                self.stack.append('`')
+            self._backquote = not self._backquote
+        if del_ws and self.tokens and self.tokens[-1] == ' ':
+            del self.tokens[-1]
+        self.tokens.append(self.string)
+        self._type = self.type
+        self._string = self.string
+        if append_ws:
+            self.tokens.append(' ')
+
+    def function_parameters(self, lineno):
+        """
+        Return a dictionary mapping parameters to defaults
+        (whitespace-normalized strings).
+        """
+        self.goto_line(lineno)
+        while self.string != 'def':
+            self.next()
+        while self.string != '(':
+            self.next()
+        name = None
+        default = None
+        parameter_tuple = None
+        self.tokens = []
+        parameters = {}
+        self.stack = [self.string]
+        self.next()
+        while 1:
+            if len(self.stack) == 1:
+                if parameter_tuple:
+                    # Just encountered ")".
+                    #print >>sys.stderr, 'parameter_tuple: %r' % self.tokens
+                    name = ''.join(self.tokens).strip()
+                    self.tokens = []
+                    parameter_tuple = None
+                if self.string in (')', ','):
+                    if name:
+                        if self.tokens:
+                            default_text = ''.join(self.tokens).strip()
+                        else:
+                            default_text = None
+                        parameters[name] = default_text
+                        self.tokens = []
+                        name = None
+                        default = None
+                    if self.string == ')':
+                        break
+                elif self.type == token.NAME:
+                    if name and default:
+                        self.note_token()
+                    else:
+                        assert name is None, (
+                            'token=%r name=%r parameters=%r stack=%r'
+                            % (self.token, name, parameters, self.stack))
+                        name = self.string
+                        #print >>sys.stderr, 'name=%r' % name
+                elif self.string == '=':
+                    assert name is not None, 'token=%r' % (self.token,)
+                    assert default is None, 'token=%r' % (self.token,)
+                    assert self.tokens == [], 'token=%r' % (self.token,)
+                    default = 1
+                    self._type = None
+                    self._string = None
+                    self._backquote = 0
+                elif name:
+                    self.note_token()
+                elif self.string == '(':
+                    parameter_tuple = 1
+                    self._type = None
+                    self._string = None
+                    self._backquote = 0
+                    self.note_token()
+                else:                   # ignore these tokens:
+                    assert (self.string in ('*', '**', '\n') 
+                            or self.type == tokenize.COMMENT), (
+                        'token=%r' % (self.token,))
+            else:
+                self.note_token()
+            self.next()
+        return parameters
+
+
+def make_docstring(doc, lineno):
+    n = pynodes.docstring()
+    if lineno:
+        # Really, only module docstrings don't have a line
+        # (@@: but maybe they should)
+        n['lineno'] = lineno
+    n.append(Text(doc))
+    return n
+
+def append_docstring(node, doc, lineno):
+    if doc:
+        node.append(make_docstring(doc, lineno))
+
+def make_class_section(name, bases, lineno, doc):
+    n = pynodes.class_section()
+    n['lineno'] = lineno
+    n.append(make_object_name(name))
+    for base in bases:
+        b = pynodes.class_base()
+        b.append(make_object_name(base))
+        n.append(b)
+    append_docstring(n, doc, lineno)
+    return n
+
+def make_object_name(name):
+    n = pynodes.object_name()
+    n.append(Text(name))
+    return n
+
+def make_function_like_section(name, lineno, doc, function_class):
+    n = function_class()
+    n['lineno'] = lineno
+    n.append(make_object_name(name))
+    append_docstring(n, doc, lineno)
+    return n
+
+def make_import_group(names, lineno, from_name=None):
+    n = pynodes.import_group()
+    n['lineno'] = lineno
+    if from_name:
+        n_from = pynodes.import_from()
+        n_from.append(Text(from_name))
+        n.append(n_from)
+    for name, alias in names:
+        n_name = pynodes.import_name()
+        n_name.append(Text(name))
+        if alias:
+            n_alias = pynodes.import_alias()
+            n_alias.append(Text(alias))
+            n_name.append(n_alias)
+        n.append(n_name)
+    return n
+
+def make_class_attribute(name, lineno):
+    n = pynodes.class_attribute()
+    n['lineno'] = lineno
+    n.append(Text(name))
+    return n
+
+def make_attribute(name, lineno):
+    n = pynodes.attribute()
+    n['lineno'] = lineno
+    n.append(make_object_name(name))
+    return n
+
+def make_parameter(name, excess_keyword=0, excess_positional=0):
+    """
+    excess_keyword and excess_positional must be either 1 or 0, and
+    not both of them can be 1.
+    """
+    n = pynodes.parameter()
+    n.append(make_object_name(name))
+    assert not excess_keyword or not excess_positional
+    if excess_keyword:
+        n['excess_keyword'] = 1
+    if excess_positional:
+        n['excess_positional'] = 1
+    return n
+
+def trim_docstring(text):
+    """
+    Trim indentation and blank lines from docstring text & return it.
+
+    See PEP 257.
+    """
+    if not text:
+        return text
+    # Convert tabs to spaces (following the normal Python rules)
+    # and split into a list of lines:
+    lines = text.expandtabs().splitlines()
+    # Determine minimum indentation (first line doesn't count):
+    indent = sys.maxint
+    for line in lines[1:]:
+        stripped = line.lstrip()
+        if stripped:
+            indent = min(indent, len(line) - len(stripped))
+    # Remove indentation (first line is special):
+    trimmed = [lines[0].strip()]
+    if indent < sys.maxint:
+        for line in lines[1:]:
+            trimmed.append(line[indent:].rstrip())
+    # Strip off trailing and leading blank lines:
+    while trimmed and not trimmed[-1]:
+        trimmed.pop()
+    while trimmed and not trimmed[0]:
+        trimmed.pop(0)
+    # Return a single string:
+    return '\n'.join(trimmed)
+
+def normalize_parameter_name(name):
+    """
+    Converts a tuple like ``('a', ('b', 'c'), 'd')`` into ``'(a, (b, c), d)'``
+    """
+    if type(name) is TupleType:
+        return '(%s)' % ', '.join([normalize_parameter_name(n) for n in name])
+    else:
+        return name
+
+if __name__ == '__main__':
+    import sys
+    args = sys.argv[1:]
+    if args[0] == '-v':
+        filename = args[1]
+        module_text = open(filename).read()
+        ast = compiler.parse(module_text)
+        visitor = compiler.visitor.ExampleASTVisitor()
+        compiler.walk(ast, visitor, walker=visitor, verbose=1)
+    else:
+        filename = args[0]
+        content = open(filename).read()
+        print parse_module(content, filename).pformat()
+

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/pynodes.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/pynodes.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/python/pynodes.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,87 @@
+#! /usr/bin/env python
+
+"""
+:Author: David Goodger
+:Contact: goodger at users.sourceforge.net
+:Revision: $Revision: 1.1.4.4 $
+:Date: $Date: 2005/01/07 13:26:05 $
+:Copyright: This module has been placed in the public domain.
+
+"""
+
+from docutils import nodes
+from docutils.nodes import Element, TextElement, Structural, Inline, Part, \
+     Text
+import types
+
+# This is the parent class of all the other pynode classes:
+class PythonStructural(Structural): pass
+
+# =====================
+#  Structural Elements
+# =====================
+
+class module_section(PythonStructural, Element): pass    
+class class_section(PythonStructural, Element): pass
+class class_base(PythonStructural, Element): pass
+class method_section(PythonStructural, Element): pass
+class attribute(PythonStructural, Element): pass
+class function_section(PythonStructural, Element): pass
+class class_attribute_section(PythonStructural, Element): pass
+class class_attribute(PythonStructural, Element): pass
+class expression_value(PythonStructural, Element): pass
+class attribute(PythonStructural, Element): pass
+
+# Structural Support Elements
+# ---------------------------
+
+class parameter_list(PythonStructural, Element): pass
+class parameter_tuple(PythonStructural, Element): pass
+class parameter_default(PythonStructural, TextElement): pass
+class import_group(PythonStructural, TextElement): pass
+class import_from(PythonStructural, TextElement): pass
+class import_name(PythonStructural, TextElement): pass
+class import_alias(PythonStructural, TextElement): pass
+class docstring(PythonStructural, Element): pass
+
+# =================
+#  Inline Elements
+# =================
+
+# These elements cannot become references until the second
+# pass.  Initially, we'll use "reference" or "name".
+
+class object_name(PythonStructural, TextElement): pass
+class parameter_list(PythonStructural, TextElement): pass
+class parameter(PythonStructural, TextElement): pass
+class parameter_default(PythonStructural, TextElement): pass
+class class_attribute(PythonStructural, TextElement): pass
+class attribute_tuple(PythonStructural, TextElement): pass
+
+# =================
+#  Unused Elements
+# =================
+
+# These were part of the model, and maybe should be in the future, but
+# aren't now.
+#class package_section(PythonStructural, Element): pass
+#class module_attribute_section(PythonStructural, Element): pass
+#class instance_attribute_section(PythonStructural, Element): pass
+#class module_attribute(PythonStructural, TextElement): pass
+#class instance_attribute(PythonStructural, TextElement): pass
+#class exception_class(PythonStructural, TextElement): pass
+#class warning_class(PythonStructural, TextElement): pass
+
+
+# Collect all the classes we've written above
+def install_node_class_names():
+    node_class_names = []
+    for name, var in globals().items():
+        if (type(var) is types.ClassType
+            and issubclass(var, PythonStructural) \
+            and name.lower() == name):
+            node_class_names.append(var.tagname or name)
+    # Register the new node names with GenericNodeVisitor and
+    # SpecificNodeVisitor:
+    nodes._add_node_class_names(node_class_names)
+install_node_class_names()

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/standalone.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/standalone.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/readers/standalone.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,53 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:05 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Standalone file Reader for the reStructuredText markup syntax.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+from docutils import frontend, readers
+from docutils.transforms import frontmatter, references
+
+
+class Reader(readers.Reader):
+
+    supported = ('standalone',)
+    """Contexts this reader supports."""
+
+    document = None
+    """A single document tree."""
+
+    settings_spec = (
+        'Standalone Reader',
+        None,
+        (('Disable the promotion of a lone top-level section title to '
+          'document title (and subsequent section title to document '
+          'subtitle promotion; enabled by default).',
+          ['--no-doc-title'],
+          {'dest': 'doctitle_xform', 'action': 'store_false', 'default': 1,
+           'validator': frontend.validate_boolean}),
+         ('Disable the bibliographic field list transform (enabled by '
+          'default).',
+          ['--no-doc-info'],
+          {'dest': 'docinfo_xform', 'action': 'store_false', 'default': 1,
+           'validator': frontend.validate_boolean}),))
+
+    config_section = 'standalone reader'
+    config_section_dependencies = ('readers',)
+
+    default_transforms = (references.Substitutions,
+                          frontmatter.DocTitle,
+                          frontmatter.DocInfo,
+                          references.ChainedTargets,
+                          references.AnonymousHyperlinks,
+                          references.IndirectHyperlinks,
+                          references.Footnotes,
+                          references.ExternalTargets,
+                          references.InternalTargets,)

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/statemachine.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/statemachine.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/statemachine.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,1466 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+A finite state machine specialized for regular-expression-based text filters,
+this module defines the following classes:
+
+- `StateMachine`, a state machine
+- `State`, a state superclass
+- `StateMachineWS`, a whitespace-sensitive version of `StateMachine`
+- `StateWS`, a state superclass for use with `StateMachineWS`
+- `SearchStateMachine`, uses `re.search()` instead of `re.match()`
+- `SearchStateMachineWS`, uses `re.search()` instead of `re.match()`
+- `ViewList`, extends standard Python lists.
+- `StringList`, string-specific ViewList.
+
+Exception classes:
+
+- `StateMachineError`
+- `UnknownStateError`
+- `DuplicateStateError`
+- `UnknownTransitionError`
+- `DuplicateTransitionError`
+- `TransitionPatternNotFound`
+- `TransitionMethodNotFound`
+- `UnexpectedIndentationError`
+- `TransitionCorrection`: Raised to switch to another transition.
+- `StateCorrection`: Raised to switch to another state & transition.
+
+Functions:
+
+- `string2lines()`: split a multi-line string into a list of one-line strings
+
+
+How To Use This Module
+======================
+(See the individual classes, methods, and attributes for details.)
+
+1. Import it: ``import statemachine`` or ``from statemachine import ...``.
+   You will also need to ``import re``.
+
+2. Derive a subclass of `State` (or `StateWS`) for each state in your state
+   machine::
+
+       class MyState(statemachine.State):
+
+   Within the state's class definition:
+
+   a) Include a pattern for each transition, in `State.patterns`::
+
+          patterns = {'atransition': r'pattern', ...}
+
+   b) Include a list of initial transitions to be set up automatically, in
+      `State.initial_transitions`::
+
+          initial_transitions = ['atransition', ...]
+
+   c) Define a method for each transition, with the same name as the
+      transition pattern::
+
+          def atransition(self, match, context, next_state):
+              # do something
+              result = [...]  # a list
+              return context, next_state, result
+              # context, next_state may be altered
+
+      Transition methods may raise an `EOFError` to cut processing short.
+
+   d) You may wish to override the `State.bof()` and/or `State.eof()` implicit
+      transition methods, which handle the beginning- and end-of-file.
+
+   e) In order to handle nested processing, you may wish to override the
+      attributes `State.nested_sm` and/or `State.nested_sm_kwargs`.
+
+      If you are using `StateWS` as a base class, in order to handle nested
+      indented blocks, you may wish to:
+
+      - override the attributes `StateWS.indent_sm`,
+        `StateWS.indent_sm_kwargs`, `StateWS.known_indent_sm`, and/or
+        `StateWS.known_indent_sm_kwargs`;
+      - override the `StateWS.blank()` method; and/or
+      - override or extend the `StateWS.indent()`, `StateWS.known_indent()`,
+        and/or `StateWS.firstknown_indent()` methods.
+
+3. Create a state machine object::
+
+       sm = StateMachine(state_classes=[MyState, ...],
+                         initial_state='MyState')
+
+4. Obtain the input text, which needs to be converted into a tab-free list of
+   one-line strings. For example, to read text from a file called
+   'inputfile'::
+
+       input_string = open('inputfile').read()
+       input_lines = statemachine.string2lines(input_string)
+
+5. Run the state machine on the input text and collect the results, a list::
+
+       results = sm.run(input_lines)
+
+6. Remove any lingering circular references::
+
+       sm.unlink()
+"""
+
+__docformat__ = 'restructuredtext'
+
+import sys
+import re
+from types import SliceType as _SliceType
+
+
+class StateMachine:
+
+    """
+    A finite state machine for text filters using regular expressions.
+
+    The input is provided in the form of a list of one-line strings (no
+    newlines). States are subclasses of the `State` class. Transitions consist
+    of regular expression patterns and transition methods, and are defined in
+    each state.
+
+    The state machine is started with the `run()` method, which returns the
+    results of processing in a list.
+    """
+
+    def __init__(self, state_classes, initial_state, debug=0):
+        """
+        Initialize a `StateMachine` object; add state objects.
+
+        Parameters:
+
+        - `state_classes`: a list of `State` (sub)classes.
+        - `initial_state`: a string, the class name of the initial state.
+        - `debug`: a boolean; produce verbose output if true (nonzero).
+        """
+
+        self.input_lines = None
+        """`StringList` of input lines (without newlines).
+        Filled by `self.run()`."""
+
+        self.input_offset = 0
+        """Offset of `self.input_lines` from the beginning of the file."""
+
+        self.line = None
+        """Current input line."""
+
+        self.line_offset = -1
+        """Current input line offset from beginning of `self.input_lines`."""
+
+        self.debug = debug
+        """Debugging mode on/off."""
+
+        self.initial_state = initial_state
+        """The name of the initial state (key to `self.states`)."""
+
+        self.current_state = initial_state
+        """The name of the current state (key to `self.states`)."""
+
+        self.states = {}
+        """Mapping of {state_name: State_object}."""
+
+        self.add_states(state_classes)
+
+        self.observers = []
+        """List of bound methods or functions to call whenever the current
+        line changes.  Observers are called with one argument, ``self``.
+        Cleared at the end of `run()`."""
+
+    def unlink(self):
+        """Remove circular references to objects no longer required."""
+        for state in self.states.values():
+            state.unlink()
+        self.states = None
+
+    def run(self, input_lines, input_offset=0, context=None,
+            input_source=None):
+        """
+        Run the state machine on `input_lines`. Return results (a list).
+
+        Reset `self.line_offset` and `self.current_state`. Run the
+        beginning-of-file transition. Input one line at a time and check for a
+        matching transition. If a match is found, call the transition method
+        and possibly change the state. Store the context returned by the
+        transition method to be passed on to the next transition matched.
+        Accumulate the results returned by the transition methods in a list.
+        Run the end-of-file transition. Finally, return the accumulated
+        results.
+
+        Parameters:
+
+        - `input_lines`: a list of strings without newlines, or `StringList`.
+        - `input_offset`: the line offset of `input_lines` from the beginning
+          of the file.
+        - `context`: application-specific storage.
+        - `input_source`: name or path of source of `input_lines`.
+        """
+        self.runtime_init()
+        if isinstance(input_lines, StringList):
+            self.input_lines = input_lines
+        else:
+            self.input_lines = StringList(input_lines, source=input_source)
+        self.input_offset = input_offset
+        self.line_offset = -1
+        self.current_state = self.initial_state
+        if self.debug:
+            print >>sys.stderr, (
+                '\nStateMachine.run: input_lines (line_offset=%s):\n| %s'
+                % (self.line_offset, '\n| '.join(self.input_lines)))
+        transitions = None
+        results = []
+        state = self.get_state()
+        try:
+            if self.debug:
+                print >>sys.stderr, ('\nStateMachine.run: bof transition')
+            context, result = state.bof(context)
+            results.extend(result)
+            while 1:
+                try:
+                    try:
+                        self.next_line()
+                        if self.debug:
+                            source, offset = self.input_lines.info(
+                                self.line_offset)
+                            print >>sys.stderr, (
+                                '\nStateMachine.run: line (source=%r, '
+                                'offset=%r):\n| %s'
+                                % (source, offset, self.line))
+                        context, next_state, result = self.check_line(
+                            context, state, transitions)
+                    except EOFError:
+                        if self.debug:
+                            print >>sys.stderr, (
+                                '\nStateMachine.run: %s.eof transition'
+                                % state.__class__.__name__)
+                        result = state.eof(context)
+                        results.extend(result)
+                        break
+                    else:
+                        results.extend(result)
+                except TransitionCorrection, exception:
+                    self.previous_line() # back up for another try
+                    transitions = (exception.args[0],)
+                    if self.debug:
+                        print >>sys.stderr, (
+                              '\nStateMachine.run: TransitionCorrection to '
+                              'state "%s", transition %s.'
+                              % (state.__class__.__name__, transitions[0]))
+                    continue
+                except StateCorrection, exception:
+                    self.previous_line() # back up for another try
+                    next_state = exception.args[0]
+                    if len(exception.args) == 1:
+                        transitions = None
+                    else:
+                        transitions = (exception.args[1],)
+                    if self.debug:
+                        print >>sys.stderr, (
+                              '\nStateMachine.run: StateCorrection to state '
+                              '"%s", transition %s.'
+                              % (next_state, transitions[0]))
+                else:
+                    transitions = None
+                state = self.get_state(next_state)
+        except:
+            if self.debug:
+                self.error()
+            raise
+        self.observers = []
+        return results
+
+    def get_state(self, next_state=None):
+        """
+        Return current state object; set it first if `next_state` given.
+
+        Parameter `next_state`: a string, the name of the next state.
+
+        Exception: `UnknownStateError` raised if `next_state` unknown.
+        """
+        if next_state:
+            if self.debug and next_state != self.current_state:
+                print >>sys.stderr, \
+                      ('\nStateMachine.get_state: Changing state from '
+                       '"%s" to "%s" (input line %s).'
+                       % (self.current_state, next_state,
+                          self.abs_line_number()))
+            self.current_state = next_state
+        try:
+            return self.states[self.current_state]
+        except KeyError:
+            raise UnknownStateError(self.current_state)
+
+    def next_line(self, n=1):
+        """Load `self.line` with the `n`'th next line and return it."""
+        try:
+            try:
+                self.line_offset += n
+                self.line = self.input_lines[self.line_offset]
+            except IndexError:
+                self.line = None
+                raise EOFError
+            return self.line
+        finally:
+            self.notify_observers()
+
+    def is_next_line_blank(self):
+        """Return 1 if the next line is blank or non-existant."""
+        try:
+            return not self.input_lines[self.line_offset + 1].strip()
+        except IndexError:
+            return 1
+
+    def at_eof(self):
+        """Return 1 if the input is at or past end-of-file."""
+        return self.line_offset >= len(self.input_lines) - 1
+
+    def at_bof(self):
+        """Return 1 if the input is at or before beginning-of-file."""
+        return self.line_offset <= 0
+
+    def previous_line(self, n=1):
+        """Load `self.line` with the `n`'th previous line and return it."""
+        self.line_offset -= n
+        if self.line_offset < 0:
+            self.line = None
+        else:
+            self.line = self.input_lines[self.line_offset]
+        self.notify_observers()
+        return self.line
+
+    def goto_line(self, line_offset):
+        """Jump to absolute line offset `line_offset`, load and return it."""
+        try:
+            try:
+                self.line_offset = line_offset - self.input_offset
+                self.line = self.input_lines[self.line_offset]
+            except IndexError:
+                self.line = None
+                raise EOFError
+            return self.line
+        finally:
+            self.notify_observers()
+
+    def get_source(self, line_offset):
+        """Return source of line at absolute line offset `line_offset`."""
+        return self.input_lines.source(line_offset - self.input_offset)
+
+    def abs_line_offset(self):
+        """Return line offset of current line, from beginning of file."""
+        return self.line_offset + self.input_offset
+
+    def abs_line_number(self):
+        """Return line number of current line (counting from 1)."""
+        return self.line_offset + self.input_offset + 1
+
+    def insert_input(self, input_lines, source):
+        self.input_lines.insert(self.line_offset + 1, '',
+                                source='internal padding')
+        self.input_lines.insert(self.line_offset + 1, '',
+                                source='internal padding')
+        self.input_lines.insert(self.line_offset + 2,
+                                StringList(input_lines, source))
+
+    def get_text_block(self, flush_left=0):
+        """
+        Return a contiguous block of text.
+
+        If `flush_left` is true, raise `UnexpectedIndentationError` if an
+        indented line is encountered before the text block ends (with a blank
+        line).
+        """
+        try:
+            block = self.input_lines.get_text_block(self.line_offset,
+                                                    flush_left)
+            self.next_line(len(block) - 1)
+            return block
+        except UnexpectedIndentationError, error:
+            block, source, lineno = error
+            self.next_line(len(block) - 1) # advance to last line of block
+            raise
+
+    def check_line(self, context, state, transitions=None):
+        """
+        Examine one line of input for a transition match & execute its method.
+
+        Parameters:
+
+        - `context`: application-dependent storage.
+        - `state`: a `State` object, the current state.
+        - `transitions`: an optional ordered list of transition names to try,
+          instead of ``state.transition_order``.
+
+        Return the values returned by the transition method:
+
+        - context: possibly modified from the parameter `context`;
+        - next state name (`State` subclass name);
+        - the result output of the transition, a list.
+
+        When there is no match, ``state.no_match()`` is called and its return
+        value is returned.
+        """
+        if transitions is None:
+            transitions =  state.transition_order
+        state_correction = None
+        if self.debug:
+            print >>sys.stderr, (
+                  '\nStateMachine.check_line: state="%s", transitions=%r.'
+                  % (state.__class__.__name__, transitions))
+        for name in transitions:
+            pattern, method, next_state = state.transitions[name]
+            match = self.match(pattern)
+            if match:
+                if self.debug:
+                    print >>sys.stderr, (
+                          '\nStateMachine.check_line: Matched transition '
+                          '"%s" in state "%s".'
+                          % (name, state.__class__.__name__))
+                return method(match, context, next_state)
+        else:
+            if self.debug:
+                print >>sys.stderr, (
+                      '\nStateMachine.check_line: No match in state "%s".'
+                      % state.__class__.__name__)
+            return state.no_match(context, transitions)
+
+    def match(self, pattern):
+        """
+        Return the result of a regular expression match.
+
+        Parameter `pattern`: an `re` compiled regular expression.
+        """
+        return pattern.match(self.line)
+
+    def add_state(self, state_class):
+        """
+        Initialize & add a `state_class` (`State` subclass) object.
+
+        Exception: `DuplicateStateError` raised if `state_class` was already
+        added.
+        """
+        statename = state_class.__name__
+        if self.states.has_key(statename):
+            raise DuplicateStateError(statename)
+        self.states[statename] = state_class(self, self.debug)
+
+    def add_states(self, state_classes):
+        """
+        Add `state_classes` (a list of `State` subclasses).
+        """
+        for state_class in state_classes:
+            self.add_state(state_class)
+
+    def runtime_init(self):
+        """
+        Initialize `self.states`.
+        """
+        for state in self.states.values():
+            state.runtime_init()
+
+    def error(self):
+        """Report error details."""
+        type, value, module, line, function = _exception_data()
+        print >>sys.stderr, '%s: %s' % (type, value)
+        print >>sys.stderr, 'input line %s' % (self.abs_line_number())
+        print >>sys.stderr, ('module %s, line %s, function %s'
+                             % (module, line, function))
+
+    def attach_observer(self, observer):
+        """
+        The `observer` parameter is a function or bound method which takes two
+        arguments, the source and offset of the current line.
+        """
+        self.observers.append(observer)
+
+    def detach_observer(self, observer):
+        self.observers.remove(observer)
+
+    def notify_observers(self):
+        for observer in self.observers:
+            try:
+                info = self.input_lines.info(self.line_offset)
+            except IndexError:
+                info = (None, None)
+            observer(*info)
+
+
+class State:
+
+    """
+    State superclass. Contains a list of transitions, and transition methods.
+
+    Transition methods all have the same signature. They take 3 parameters:
+
+    - An `re` match object. ``match.string`` contains the matched input line,
+      ``match.start()`` gives the start index of the match, and
+      ``match.end()`` gives the end index.
+    - A context object, whose meaning is application-defined (initial value
+      ``None``). It can be used to store any information required by the state
+      machine, and the retured context is passed on to the next transition
+      method unchanged.
+    - The name of the next state, a string, taken from the transitions list;
+      normally it is returned unchanged, but it may be altered by the
+      transition method if necessary.
+
+    Transition methods all return a 3-tuple:
+
+    - A context object, as (potentially) modified by the transition method.
+    - The next state name (a return value of ``None`` means no state change).
+    - The processing result, a list, which is accumulated by the state
+      machine.
+
+    Transition methods may raise an `EOFError` to cut processing short.
+
+    There are two implicit transitions, and corresponding transition methods
+    are defined: `bof()` handles the beginning-of-file, and `eof()` handles
+    the end-of-file. These methods have non-standard signatures and return
+    values. `bof()` returns the initial context and results, and may be used
+    to return a header string, or do any other processing needed. `eof()`
+    should handle any remaining context and wrap things up; it returns the
+    final processing result.
+
+    Typical applications need only subclass `State` (or a subclass), set the
+    `patterns` and `initial_transitions` class attributes, and provide
+    corresponding transition methods. The default object initialization will
+    take care of constructing the list of transitions.
+    """
+
+    patterns = None
+    """
+    {Name: pattern} mapping, used by `make_transition()`. Each pattern may
+    be a string or a compiled `re` pattern. Override in subclasses.
+    """
+
+    initial_transitions = None
+    """
+    A list of transitions to initialize when a `State` is instantiated.
+    Each entry is either a transition name string, or a (transition name, next
+    state name) pair. See `make_transitions()`. Override in subclasses.
+    """
+
+    nested_sm = None
+    """
+    The `StateMachine` class for handling nested processing.
+
+    If left as ``None``, `nested_sm` defaults to the class of the state's
+    controlling state machine. Override it in subclasses to avoid the default.
+    """
+
+    nested_sm_kwargs = None
+    """
+    Keyword arguments dictionary, passed to the `nested_sm` constructor.
+
+    Two keys must have entries in the dictionary:
+
+    - Key 'state_classes' must be set to a list of `State` classes.
+    - Key 'initial_state' must be set to the name of the initial state class.
+
+    If `nested_sm_kwargs` is left as ``None``, 'state_classes' defaults to the
+    class of the current state, and 'initial_state' defaults to the name of
+    the class of the current state. Override in subclasses to avoid the
+    defaults.
+    """
+
+    def __init__(self, state_machine, debug=0):
+        """
+        Initialize a `State` object; make & add initial transitions.
+
+        Parameters:
+
+        - `statemachine`: the controlling `StateMachine` object.
+        - `debug`: a boolean; produce verbose output if true (nonzero).
+        """
+
+        self.transition_order = []
+        """A list of transition names in search order."""
+
+        self.transitions = {}
+        """
+        A mapping of transition names to 3-tuples containing
+        (compiled_pattern, transition_method, next_state_name). Initialized as
+        an instance attribute dynamically (instead of as a class attribute)
+        because it may make forward references to patterns and methods in this
+        or other classes.
+        """
+
+        self.add_initial_transitions()
+
+        self.state_machine = state_machine
+        """A reference to the controlling `StateMachine` object."""
+
+        self.debug = debug
+        """Debugging mode on/off."""
+
+        if self.nested_sm is None:
+            self.nested_sm = self.state_machine.__class__
+        if self.nested_sm_kwargs is None:
+            self.nested_sm_kwargs = {'state_classes': [self.__class__],
+                                     'initial_state': self.__class__.__name__}
+
+    def runtime_init(self):
+        """
+        Initialize this `State` before running the state machine; called from
+        `self.state_machine.run()`.
+        """
+        pass
+
+    def unlink(self):
+        """Remove circular references to objects no longer required."""
+        self.state_machine = None
+
+    def add_initial_transitions(self):
+        """Make and add transitions listed in `self.initial_transitions`."""
+        if self.initial_transitions:
+            names, transitions = self.make_transitions(
+                  self.initial_transitions)
+            self.add_transitions(names, transitions)
+
+    def add_transitions(self, names, transitions):
+        """
+        Add a list of transitions to the start of the transition list.
+
+        Parameters:
+
+        - `names`: a list of transition names.
+        - `transitions`: a mapping of names to transition tuples.
+
+        Exceptions: `DuplicateTransitionError`, `UnknownTransitionError`.
+        """
+        for name in names:
+            if self.transitions.has_key(name):
+                raise DuplicateTransitionError(name)
+            if not transitions.has_key(name):
+                raise UnknownTransitionError(name)
+        self.transition_order[:0] = names
+        self.transitions.update(transitions)
+
+    def add_transition(self, name, transition):
+        """
+        Add a transition to the start of the transition list.
+
+        Parameter `transition`: a ready-made transition 3-tuple.
+
+        Exception: `DuplicateTransitionError`.
+        """
+        if self.transitions.has_key(name):
+            raise DuplicateTransitionError(name)
+        self.transition_order[:0] = [name]
+        self.transitions[name] = transition
+
+    def remove_transition(self, name):
+        """
+        Remove a transition by `name`.
+
+        Exception: `UnknownTransitionError`.
+        """
+        try:
+            del self.transitions[name]
+            self.transition_order.remove(name)
+        except:
+            raise UnknownTransitionError(name)
+
+    def make_transition(self, name, next_state=None):
+        """
+        Make & return a transition tuple based on `name`.
+
+        This is a convenience function to simplify transition creation.
+
+        Parameters:
+
+        - `name`: a string, the name of the transition pattern & method. This
+          `State` object must have a method called '`name`', and a dictionary
+          `self.patterns` containing a key '`name`'.
+        - `next_state`: a string, the name of the next `State` object for this
+          transition. A value of ``None`` (or absent) implies no state change
+          (i.e., continue with the same state).
+
+        Exceptions: `TransitionPatternNotFound`, `TransitionMethodNotFound`.
+        """
+        if next_state is None:
+            next_state = self.__class__.__name__
+        try:
+            pattern = self.patterns[name]
+            if not hasattr(pattern, 'match'):
+                pattern = re.compile(pattern)
+        except KeyError:
+            raise TransitionPatternNotFound(
+                  '%s.patterns[%r]' % (self.__class__.__name__, name))
+        try:
+            method = getattr(self, name)
+        except AttributeError:
+            raise TransitionMethodNotFound(
+                  '%s.%s' % (self.__class__.__name__, name))
+        return (pattern, method, next_state)
+
+    def make_transitions(self, name_list):
+        """
+        Return a list of transition names and a transition mapping.
+
+        Parameter `name_list`: a list, where each entry is either a transition
+        name string, or a 1- or 2-tuple (transition name, optional next state
+        name).
+        """
+        stringtype = type('')
+        names = []
+        transitions = {}
+        for namestate in name_list:
+            if type(namestate) is stringtype:
+                transitions[namestate] = self.make_transition(namestate)
+                names.append(namestate)
+            else:
+                transitions[namestate[0]] = self.make_transition(*namestate)
+                names.append(namestate[0])
+        return names, transitions
+
+    def no_match(self, context, transitions):
+        """
+        Called when there is no match from `StateMachine.check_line()`.
+
+        Return the same values returned by transition methods:
+
+        - context: unchanged;
+        - next state name: ``None``;
+        - empty result list.
+
+        Override in subclasses to catch this event.
+        """
+        return context, None, []
+
+    def bof(self, context):
+        """
+        Handle beginning-of-file. Return unchanged `context`, empty result.
+
+        Override in subclasses.
+
+        Parameter `context`: application-defined storage.
+        """
+        return context, []
+
+    def eof(self, context):
+        """
+        Handle end-of-file. Return empty result.
+
+        Override in subclasses.
+
+        Parameter `context`: application-defined storage.
+        """
+        return []
+
+    def nop(self, match, context, next_state):
+        """
+        A "do nothing" transition method.
+
+        Return unchanged `context` & `next_state`, empty result. Useful for
+        simple state changes (actionless transitions).
+        """
+        return context, next_state, []
+
+
+class StateMachineWS(StateMachine):
+
+    """
+    `StateMachine` subclass specialized for whitespace recognition.
+
+    There are three methods provided for extracting indented text blocks:
+    
+    - `get_indented()`: use when the indent is unknown.
+    - `get_known_indented()`: use when the indent is known for all lines.
+    - `get_first_known_indented()`: use when only the first line's indent is
+      known.
+    """
+
+    def get_indented(self, until_blank=0, strip_indent=1):
+        """
+        Return a block of indented lines of text, and info.
+
+        Extract an indented block where the indent is unknown for all lines.
+
+        :Parameters:
+            - `until_blank`: Stop collecting at the first blank line if true
+              (1).
+            - `strip_indent`: Strip common leading indent if true (1,
+              default).
+
+        :Return:
+            - the indented block (a list of lines of text),
+            - its indent,
+            - its first line offset from BOF, and
+            - whether or not it finished with a blank line.
+        """
+        offset = self.abs_line_offset()
+        indented, indent, blank_finish = self.input_lines.get_indented(
+              self.line_offset, until_blank, strip_indent)
+        if indented:
+            self.next_line(len(indented) - 1) # advance to last indented line
+        while indented and not indented[0].strip():
+            indented.trim_start()
+            offset += 1
+        return indented, indent, offset, blank_finish
+
+    def get_known_indented(self, indent, until_blank=0, strip_indent=1):
+        """
+        Return an indented block and info.
+
+        Extract an indented block where the indent is known for all lines.
+        Starting with the current line, extract the entire text block with at
+        least `indent` indentation (which must be whitespace, except for the
+        first line).
+
+        :Parameters:
+            - `indent`: The number of indent columns/characters.
+            - `until_blank`: Stop collecting at the first blank line if true
+              (1).
+            - `strip_indent`: Strip `indent` characters of indentation if true
+              (1, default).
+
+        :Return:
+            - the indented block,
+            - its first line offset from BOF, and
+            - whether or not it finished with a blank line.
+        """
+        offset = self.abs_line_offset()
+        indented, indent, blank_finish = self.input_lines.get_indented(
+              self.line_offset, until_blank, strip_indent,
+              block_indent=indent)
+        self.next_line(len(indented) - 1) # advance to last indented line
+        while indented and not indented[0].strip():
+            indented.trim_start()
+            offset += 1
+        return indented, offset, blank_finish
+
+    def get_first_known_indented(self, indent, until_blank=0, strip_indent=1,
+                                 strip_top=1):
+        """
+        Return an indented block and info.
+
+        Extract an indented block where the indent is known for the first line
+        and unknown for all other lines.
+
+        :Parameters:
+            - `indent`: The first line's indent (# of columns/characters).
+            - `until_blank`: Stop collecting at the first blank line if true
+              (1).
+            - `strip_indent`: Strip `indent` characters of indentation if true
+              (1, default).
+            - `strip_top`: Strip blank lines from the beginning of the block.
+
+        :Return:
+            - the indented block,
+            - its indent,
+            - its first line offset from BOF, and
+            - whether or not it finished with a blank line.
+        """
+        offset = self.abs_line_offset()
+        indented, indent, blank_finish = self.input_lines.get_indented(
+              self.line_offset, until_blank, strip_indent,
+              first_indent=indent)
+        self.next_line(len(indented) - 1) # advance to last indented line
+        if strip_top:
+            while indented and not indented[0].strip():
+                indented.trim_start()
+                offset += 1
+        return indented, indent, offset, blank_finish
+
+
+class StateWS(State):
+
+    """
+    State superclass specialized for whitespace (blank lines & indents).
+
+    Use this class with `StateMachineWS`.  The transitions 'blank' (for blank
+    lines) and 'indent' (for indented text blocks) are added automatically,
+    before any other transitions.  The transition method `blank()` handles
+    blank lines and `indent()` handles nested indented blocks.  Indented
+    blocks trigger a new state machine to be created by `indent()` and run.
+    The class of the state machine to be created is in `indent_sm`, and the
+    constructor keyword arguments are in the dictionary `indent_sm_kwargs`.
+
+    The methods `known_indent()` and `firstknown_indent()` are provided for
+    indented blocks where the indent (all lines' and first line's only,
+    respectively) is known to the transition method, along with the attributes
+    `known_indent_sm` and `known_indent_sm_kwargs`.  Neither transition method
+    is triggered automatically.
+    """
+
+    indent_sm = None
+    """
+    The `StateMachine` class handling indented text blocks.
+
+    If left as ``None``, `indent_sm` defaults to the value of
+    `State.nested_sm`.  Override it in subclasses to avoid the default.
+    """
+
+    indent_sm_kwargs = None
+    """
+    Keyword arguments dictionary, passed to the `indent_sm` constructor.
+
+    If left as ``None``, `indent_sm_kwargs` defaults to the value of
+    `State.nested_sm_kwargs`. Override it in subclasses to avoid the default.
+    """
+
+    known_indent_sm = None
+    """
+    The `StateMachine` class handling known-indented text blocks.
+
+    If left as ``None``, `known_indent_sm` defaults to the value of
+    `indent_sm`.  Override it in subclasses to avoid the default.
+    """
+
+    known_indent_sm_kwargs = None
+    """
+    Keyword arguments dictionary, passed to the `known_indent_sm` constructor.
+
+    If left as ``None``, `known_indent_sm_kwargs` defaults to the value of
+    `indent_sm_kwargs`. Override it in subclasses to avoid the default.
+    """
+
+    ws_patterns = {'blank': ' *$',
+                   'indent': ' +'}
+    """Patterns for default whitespace transitions.  May be overridden in
+    subclasses."""
+
+    ws_initial_transitions = ('blank', 'indent')
+    """Default initial whitespace transitions, added before those listed in
+    `State.initial_transitions`.  May be overridden in subclasses."""
+
+    def __init__(self, state_machine, debug=0):
+        """
+        Initialize a `StateSM` object; extends `State.__init__()`.
+
+        Check for indent state machine attributes, set defaults if not set.
+        """
+        State.__init__(self, state_machine, debug)
+        if self.indent_sm is None:
+            self.indent_sm = self.nested_sm
+        if self.indent_sm_kwargs is None:
+            self.indent_sm_kwargs = self.nested_sm_kwargs
+        if self.known_indent_sm is None:
+            self.known_indent_sm = self.indent_sm
+        if self.known_indent_sm_kwargs is None:
+            self.known_indent_sm_kwargs = self.indent_sm_kwargs
+
+    def add_initial_transitions(self):
+        """
+        Add whitespace-specific transitions before those defined in subclass.
+
+        Extends `State.add_initial_transitions()`.
+        """
+        State.add_initial_transitions(self)
+        if self.patterns is None:
+            self.patterns = {}
+        self.patterns.update(self.ws_patterns)
+        names, transitions = self.make_transitions(
+            self.ws_initial_transitions)
+        self.add_transitions(names, transitions)
+
+    def blank(self, match, context, next_state):
+        """Handle blank lines. Does nothing. Override in subclasses."""
+        return self.nop(match, context, next_state)
+
+    def indent(self, match, context, next_state):
+        """
+        Handle an indented text block. Extend or override in subclasses.
+
+        Recursively run the registered state machine for indented blocks
+        (`self.indent_sm`).
+        """
+        indented, indent, line_offset, blank_finish = \
+              self.state_machine.get_indented()
+        sm = self.indent_sm(debug=self.debug, **self.indent_sm_kwargs)
+        results = sm.run(indented, input_offset=line_offset)
+        return context, next_state, results
+
+    def known_indent(self, match, context, next_state):
+        """
+        Handle a known-indent text block. Extend or override in subclasses.
+
+        Recursively run the registered state machine for known-indent indented
+        blocks (`self.known_indent_sm`). The indent is the length of the
+        match, ``match.end()``.
+        """
+        indented, line_offset, blank_finish = \
+              self.state_machine.get_known_indented(match.end())
+        sm = self.known_indent_sm(debug=self.debug,
+                                 **self.known_indent_sm_kwargs)
+        results = sm.run(indented, input_offset=line_offset)
+        return context, next_state, results
+
+    def first_known_indent(self, match, context, next_state):
+        """
+        Handle an indented text block (first line's indent known).
+
+        Extend or override in subclasses.
+
+        Recursively run the registered state machine for known-indent indented
+        blocks (`self.known_indent_sm`). The indent is the length of the
+        match, ``match.end()``.
+        """
+        indented, line_offset, blank_finish = \
+              self.state_machine.get_first_known_indented(match.end())
+        sm = self.known_indent_sm(debug=self.debug,
+                                 **self.known_indent_sm_kwargs)
+        results = sm.run(indented, input_offset=line_offset)
+        return context, next_state, results
+
+
+class _SearchOverride:
+
+    """
+    Mix-in class to override `StateMachine` regular expression behavior.
+
+    Changes regular expression matching, from the default `re.match()`
+    (succeeds only if the pattern matches at the start of `self.line`) to
+    `re.search()` (succeeds if the pattern matches anywhere in `self.line`).
+    When subclassing a `StateMachine`, list this class **first** in the
+    inheritance list of the class definition.
+    """
+
+    def match(self, pattern):
+        """
+        Return the result of a regular expression search.
+
+        Overrides `StateMachine.match()`.
+
+        Parameter `pattern`: `re` compiled regular expression.
+        """
+        return pattern.search(self.line)
+
+
+class SearchStateMachine(_SearchOverride, StateMachine):
+    """`StateMachine` which uses `re.search()` instead of `re.match()`."""
+    pass
+
+
+class SearchStateMachineWS(_SearchOverride, StateMachineWS):
+    """`StateMachineWS` which uses `re.search()` instead of `re.match()`."""
+    pass
+
+
+class ViewList:
+
+    """
+    List with extended functionality: slices of ViewList objects are child
+    lists, linked to their parents. Changes made to a child list also affect
+    the parent list.  A child list is effectively a "view" (in the SQL sense)
+    of the parent list.  Changes to parent lists, however, do *not* affect
+    active child lists.  If a parent list is changed, any active child lists
+    should be recreated.
+
+    The start and end of the slice can be trimmed using the `trim_start()` and
+    `trim_end()` methods, without affecting the parent list.  The link between
+    child and parent lists can be broken by calling `disconnect()` on the
+    child list.
+
+    Also, ViewList objects keep track of the source & offset of each item. 
+    This information is accessible via the `source()`, `offset()`, and
+    `info()` methods.
+    """
+
+    def __init__(self, initlist=None, source=None, items=None,
+                 parent=None, parent_offset=None):
+        self.data = []
+        """The actual list of data, flattened from various sources."""
+
+        self.items = []
+        """A list of (source, offset) pairs, same length as `self.data`: the
+        source of each line and the offset of each line from the beginning of
+        its source."""
+
+        self.parent = parent
+        """The parent list."""
+
+        self.parent_offset = parent_offset
+        """Offset of this list from the beginning of the parent list."""
+
+        if isinstance(initlist, ViewList):
+            self.data = initlist.data[:]
+            self.items = initlist.items[:]
+        elif initlist is not None:
+            self.data = list(initlist)
+            if items:
+                self.items = items
+            else:
+                self.items = [(source, i) for i in range(len(initlist))]
+        assert len(self.data) == len(self.items), 'data mismatch'
+
+    def __str__(self):
+        return str(self.data)
+
+    def __repr__(self):
+        return '%s(%s, items=%s)' % (self.__class__.__name__,
+                                     self.data, self.items)
+
+    def __lt__(self, other): return self.data <  self.__cast(other)
+    def __le__(self, other): return self.data <= self.__cast(other)
+    def __eq__(self, other): return self.data == self.__cast(other)
+    def __ne__(self, other): return self.data != self.__cast(other)
+    def __gt__(self, other): return self.data >  self.__cast(other)
+    def __ge__(self, other): return self.data >= self.__cast(other)
+    def __cmp__(self, other): return cmp(self.data, self.__cast(other))
+
+    def __cast(self, other):
+        if isinstance(other, ViewList):
+            return other.data
+        else:
+            return other
+
+    def __contains__(self, item): return item in self.data
+    def __len__(self): return len(self.data)
+
+    # The __getitem__()/__setitem__() methods check whether the index
+    # is a slice first, since native list objects start supporting
+    # them directly in Python 2.3 (no exception is raised when
+    # indexing a list with a slice object; they just work).
+
+    def __getitem__(self, i):
+        if isinstance(i, _SliceType):
+            assert i.step in (None, 1),  'cannot handle slice with stride'
+            return self.__class__(self.data[i.start:i.stop],
+                                  items=self.items[i.start:i.stop],
+                                  parent=self, parent_offset=i.start)
+        else:
+            return self.data[i]
+
+    def __setitem__(self, i, item):
+        if isinstance(i, _SliceType):
+            assert i.step in (None, 1), 'cannot handle slice with stride'
+            if not isinstance(item, ViewList):
+                raise TypeError('assigning non-ViewList to ViewList slice')
+            self.data[i.start:i.stop] = item.data
+            self.items[i.start:i.stop] = item.items
+            assert len(self.data) == len(self.items), 'data mismatch'
+            if self.parent:
+                self.parent[i.start + self.parent_offset
+                            : i.stop + self.parent_offset] = item
+        else:
+            self.data[i] = item
+            if self.parent:
+                self.parent[i + self.parent_offset] = item
+
+    def __delitem__(self, i):
+        try:
+            del self.data[i]
+            del self.items[i]
+            if self.parent:
+                del self.parent[i + self.parent_offset]
+        except TypeError:
+            assert i.step is None, 'cannot handle slice with stride'
+            del self.data[i.start:i.stop]
+            del self.items[i.start:i.stop]
+            if self.parent:
+                del self.parent[i.start + self.parent_offset
+                                : i.stop + self.parent_offset]
+
+    def __add__(self, other):
+        if isinstance(other, ViewList):
+            return self.__class__(self.data + other.data,
+                                  items=(self.items + other.items))
+        else:
+            raise TypeError('adding non-ViewList to a ViewList')
+
+    def __radd__(self, other):
+        if isinstance(other, ViewList):
+            return self.__class__(other.data + self.data,
+                                  items=(other.items + self.items))
+        else:
+            raise TypeError('adding ViewList to a non-ViewList')
+
+    def __iadd__(self, other):
+        if isinstance(other, ViewList):
+            self.data += other.data
+        else:
+            raise TypeError('argument to += must be a ViewList')
+        return self
+
+    def __mul__(self, n):
+        return self.__class__(self.data * n, items=(self.items * n))
+
+    __rmul__ = __mul__
+
+    def __imul__(self, n):
+        self.data *= n
+        self.items *= n
+        return self
+
+    def extend(self, other):
+        if not isinstance(other, ViewList):
+            raise TypeError('extending a ViewList with a non-ViewList')
+        if self.parent:
+            self.parent.insert(len(self.data) + self.parent_offset, other)
+        self.data.extend(other.data)
+        self.items.extend(other.items)
+
+    def append(self, item, source=None, offset=0):
+        if source is None:
+            self.extend(item)
+        else:
+            if self.parent:
+                self.parent.insert(len(self.data) + self.parent_offset, item,
+                                   source, offset)
+            self.data.append(item)
+            self.items.append((source, offset))
+
+    def insert(self, i, item, source=None, offset=0):
+        if source is None:
+            if not isinstance(item, ViewList):
+                raise TypeError('inserting non-ViewList with no source given')
+            self.data[i:i] = item.data
+            self.items[i:i] = item.items
+            if self.parent:
+                index = (len(self.data) + i) % len(self.data)
+                self.parent.insert(index + self.parent_offset, item)
+        else:
+            self.data.insert(i, item)
+            self.items.insert(i, (source, offset))
+            if self.parent:
+                index = (len(self.data) + i) % len(self.data)
+                self.parent.insert(index + self.parent_offset, item,
+                                   source, offset)
+
+    def pop(self, i=-1):
+        if self.parent:
+            index = (len(self.data) + i) % len(self.data)
+            self.parent.pop(index + self.parent_offset)
+        self.items.pop(i)
+        return self.data.pop(i)
+
+    def trim_start(self, n=1):
+        """
+        Remove items from the start of the list, without touching the parent.
+        """
+        if n > len(self.data):
+            raise IndexError("Size of trim too large; can't trim %s items "
+                             "from a list of size %s." % (n, len(self.data)))
+        elif n < 0:
+            raise IndexError('Trim size must be >= 0.')
+        del self.data[:n]
+        del self.items[:n]
+        if self.parent:
+            self.parent_offset += n
+
+    def trim_end(self, n=1):
+        """
+        Remove items from the end of the list, without touching the parent.
+        """
+        if n > len(self.data):
+            raise IndexError("Size of trim too large; can't trim %s items "
+                             "from a list of size %s." % (n, len(self.data)))
+        elif n < 0:
+            raise IndexError('Trim size must be >= 0.')
+        del self.data[-n:]
+        del self.items[-n:]
+
+    def remove(self, item):
+        index = self.index(item)
+        del self[index]
+
+    def count(self, item): return self.data.count(item)
+    def index(self, item): return self.data.index(item)
+
+    def reverse(self):
+        self.data.reverse()
+        self.items.reverse()
+        self.parent = None
+
+    def sort(self, *args):
+        tmp = zip(self.data, self.items)
+        tmp.sort(*args)
+        self.data = [entry[0] for entry in tmp]
+        self.items = [entry[1] for entry in tmp]
+        self.parent = None
+
+    def info(self, i):
+        """Return source & offset for index `i`."""
+        try:
+            return self.items[i]
+        except IndexError:
+            if i == len(self.data):     # Just past the end
+                return self.items[i - 1][0], None
+            else:
+                raise
+
+    def source(self, i):
+        """Return source for index `i`."""
+        return self.info(i)[0]
+
+    def offset(self, i):
+        """Return offset for index `i`."""
+        return self.info(i)[1]
+
+    def disconnect(self):
+        """Break link between this list and parent list."""
+        self.parent = None
+
+
+class StringList(ViewList):
+
+    """A `ViewList` with string-specific methods."""
+
+    def trim_left(self, length, start=0, end=sys.maxint):
+        """
+        Trim `length` characters off the beginning of each item, in-place,
+        from index `start` to `end`.  No whitespace-checking is done on the
+        trimmed text.  Does not affect slice parent.
+        """
+        self.data[start:end] = [line[length:]
+                                for line in self.data[start:end]]
+
+    def get_text_block(self, start, flush_left=0):
+        """
+        Return a contiguous block of text.
+
+        If `flush_left` is true, raise `UnexpectedIndentationError` if an
+        indented line is encountered before the text block ends (with a blank
+        line).
+        """
+        end = start
+        last = len(self.data)
+        while end < last:
+            line = self.data[end]
+            if not line.strip():
+                break
+            if flush_left and (line[0] == ' '):
+                source, offset = self.info(end)
+                raise UnexpectedIndentationError(self[start:end], source,
+                                                 offset + 1)
+            end += 1
+        return self[start:end]
+
+    def get_indented(self, start=0, until_blank=0, strip_indent=1,
+                     block_indent=None, first_indent=None):
+        """
+        Extract and return a StringList of indented lines of text.
+
+        Collect all lines with indentation, determine the minimum indentation,
+        remove the minimum indentation from all indented lines (unless
+        `strip_indent` is false), and return them. All lines up to but not
+        including the first unindented line will be returned.
+
+        :Parameters:
+          - `start`: The index of the first line to examine.
+          - `until_blank`: Stop collecting at the first blank line if true.
+          - `strip_indent`: Strip common leading indent if true (default).
+          - `block_indent`: The indent of the entire block, if known.
+          - `first_indent`: The indent of the first line, if known.
+
+        :Return:
+          - a StringList of indented lines with mininum indent removed;
+          - the amount of the indent;
+          - a boolean: did the indented block finish with a blank line or EOF?
+        """
+        indent = block_indent           # start with None if unknown
+        end = start
+        if block_indent is not None and first_indent is None:
+            first_indent = block_indent
+        if first_indent is not None:
+            end += 1
+        last = len(self.data)
+        while end < last:
+            line = self.data[end]
+            if line and (line[0] != ' '
+                         or (block_indent is not None
+                             and line[:block_indent].strip())):
+                # Line not indented or insufficiently indented.
+                # Block finished properly iff the last indented line blank:
+                blank_finish = ((end > start)
+                                and not self.data[end - 1].strip())
+                break
+            stripped = line.lstrip()
+            if not stripped:            # blank line
+                if until_blank:
+                    blank_finish = 1
+                    break
+            elif block_indent is None:
+                line_indent = len(line) - len(stripped)
+                if indent is None:
+                    indent = line_indent
+                else:
+                    indent = min(indent, line_indent)
+            end += 1
+        else:
+            blank_finish = 1            # block ends at end of lines
+        block = self[start:end]
+        if first_indent is not None and block:
+            block.data[0] = block.data[0][first_indent:]
+        if indent and strip_indent:
+            block.trim_left(indent, start=(first_indent is not None))
+        return block, indent or 0, blank_finish
+
+    def get_2D_block(self, top, left, bottom, right, strip_indent=1):
+        block = self[top:bottom]
+        indent = right
+        for i in range(len(block.data)):
+            block.data[i] = line = block.data[i][left:right].rstrip()
+            if line:
+                indent = min(indent, len(line) - len(line.lstrip()))
+        if strip_indent and 0 < indent < right:
+            block.data = [line[indent:] for line in block.data]
+        return block
+
+
+class StateMachineError(Exception): pass
+class UnknownStateError(StateMachineError): pass
+class DuplicateStateError(StateMachineError): pass
+class UnknownTransitionError(StateMachineError): pass
+class DuplicateTransitionError(StateMachineError): pass
+class TransitionPatternNotFound(StateMachineError): pass
+class TransitionMethodNotFound(StateMachineError): pass
+class UnexpectedIndentationError(StateMachineError): pass
+
+
+class TransitionCorrection(Exception):
+
+    """
+    Raise from within a transition method to switch to another transition.
+
+    Raise with one argument, the new transition name.
+    """
+
+
+class StateCorrection(Exception):
+
+    """
+    Raise from within a transition method to switch to another state.
+
+    Raise with one or two arguments: new state name, and an optional new
+    transition name.
+    """
+
+
+def string2lines(astring, tab_width=8, convert_whitespace=0,
+                 whitespace=re.compile('[\v\f]')):
+    """
+    Return a list of one-line strings with tabs expanded and no newlines.
+
+    Each tab is expanded with between 1 and `tab_width` spaces, so that the
+    next character's index becomes a multiple of `tab_width` (8 by default).
+
+    Parameters:
+
+    - `astring`: a multi-line string.
+    - `tab_width`: the number of columns between tab stops.
+    - `convert_whitespace`: convert form feeds and vertical tabs to spaces?
+    """
+    if convert_whitespace:
+        astring = whitespace.sub(' ', astring)
+    return [s.expandtabs(tab_width) for s in astring.splitlines()]
+
+def _exception_data():
+    """
+    Return exception information:
+
+    - the exception's class name;
+    - the exception object;
+    - the name of the file containing the offending code;
+    - the line number of the offending code;
+    - the function name of the offending code.
+    """
+    type, value, traceback = sys.exc_info()
+    while traceback.tb_next:
+        traceback = traceback.tb_next
+    code = traceback.tb_frame.f_code
+    return (type.__name__, value, code.co_filename, traceback.tb_lineno,
+            code.co_name)

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,181 @@
+# Authors: David Goodger, Ueli Schlaepfer
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:05 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This package contains modules for standard tree transforms available
+to Docutils components. Tree transforms serve a variety of purposes:
+
+- To tie up certain syntax-specific "loose ends" that remain after the
+  initial parsing of the input plaintext. These transforms are used to
+  supplement a limited syntax.
+
+- To automate the internal linking of the document tree (hyperlink
+  references, footnote references, etc.).
+
+- To extract useful information from the document tree. These
+  transforms may be used to construct (for example) indexes and tables
+  of contents.
+
+Each transform is an optional step that a Docutils Reader may choose to
+perform on the parsed document, depending on the input context. A Docutils
+Reader may also perform Reader-specific transforms before or after performing
+these standard transforms.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+from docutils import languages, ApplicationError, TransformSpec
+
+
+class TransformError(ApplicationError): pass
+
+
+class Transform:
+
+    """
+    Docutils transform component abstract base class.
+    """
+
+    default_priority = None
+    """Numerical priority of this transform, 0 through 999 (override)."""
+
+    def __init__(self, document, startnode=None):
+        """
+        Initial setup for in-place document transforms.
+        """
+
+        self.document = document
+        """The document tree to transform."""
+
+        self.startnode = startnode
+        """Node from which to begin the transform.  For many transforms which
+        apply to the document as a whole, `startnode` is not set (i.e. its
+        value is `None`)."""
+
+        self.language = languages.get_language(
+            document.settings.language_code)
+        """Language module local to this document."""
+        
+
+    def apply(self):
+        """Override to apply the transform to the document tree."""
+        raise NotImplementedError('subclass must override this method')
+
+
+class Transformer(TransformSpec):
+
+    """
+    Stores transforms (`Transform` classes) and applies them to document
+    trees.  Also keeps track of components by component type name.
+    """
+
+    from docutils.transforms import universal
+
+    default_transforms = (universal.Decorations,
+                          universal.FinalChecks,
+                          universal.Messages,
+                          universal.FilterMessages)
+    """These transforms are applied to all document trees."""
+
+    def __init__(self, document):
+        self.transforms = []
+        """List of transforms to apply.  Each item is a 3-tuple:
+        ``(priority string, transform class, pending node or None)``."""
+
+        self.unknown_reference_resolvers = []
+        """List of hook functions which assist in resolving references"""
+
+        self.document = document
+        """The `nodes.document` object this Transformer is attached to."""
+
+        self.applied = []
+        """Transforms already applied, in order."""
+
+        self.sorted = 0
+        """Boolean: is `self.tranforms` sorted?"""
+
+        self.components = {}
+        """Mapping of component type name to component object.  Set by
+        `self.populate_from_components()`."""
+
+        self.serialno = 0
+        """Internal serial number to keep track of the add order of
+        transforms."""
+
+    def add_transform(self, transform_class, priority=None):
+        """
+        Store a single transform.  Use `priority` to override the default.
+        """
+        if priority is None:
+            priority = transform_class.default_priority
+        priority_string = self.get_priority_string(priority)
+        self.transforms.append((priority_string, transform_class, None))
+        self.sorted = 0
+
+    def add_transforms(self, transform_list):
+        """Store multiple transforms, with default priorities."""
+        for transform_class in transform_list:
+            priority_string = self.get_priority_string(
+                transform_class.default_priority)
+            self.transforms.append((priority_string, transform_class, None))
+        self.sorted = 0
+
+    def add_pending(self, pending, priority=None):
+        """Store a transform with an associated `pending` node."""
+        transform_class = pending.transform
+        if priority is None:
+            priority = transform_class.default_priority
+        priority_string = self.get_priority_string(priority)
+        self.transforms.append((priority_string, transform_class, pending))
+        self.sorted = 0
+
+    def get_priority_string(self, priority):
+        """
+        Return a string, `priority` combined with `self.serialno`.
+
+        This ensures FIFO order on transforms with identical priority.
+        """
+        self.serialno += 1
+        return '%03d-%03d' % (priority, self.serialno)
+
+    def populate_from_components(self, components):
+        """
+        Store each component's default transforms, with default priorities.
+        Also, store components by type name in a mapping for later lookup.
+        """
+        self.add_transforms(self.default_transforms)
+        for component in components:
+            if component is None:
+                continue
+            self.add_transforms(component.default_transforms)
+            self.components[component.component_type] = component
+        self.sorted = 0
+        # Setup all of the reference resolvers for this transformer. Each
+        # component of this transformer is able to register its own helper
+        # functions to help resolve references.
+        unknown_reference_resolvers = []
+        for i in components:
+            unknown_reference_resolvers.extend(i.unknown_reference_resolvers)
+        decorated_list = [(f.priority, f) for f in unknown_reference_resolvers]
+        decorated_list.sort()
+        self.unknown_reference_resolvers.extend([f[1] for f in decorated_list])
+
+
+    def apply_transforms(self):
+        """Apply all of the stored transforms, in priority order."""
+        self.document.reporter.attach_observer(
+            self.document.note_transform_message)
+        while self.transforms:
+            if not self.sorted:
+                # Unsorted initially, and whenever a transform is added.
+                self.transforms.sort()
+                self.transforms.reverse()
+                self.sorted = 1
+            priority, transform_class, pending = self.transforms.pop()
+            transform = transform_class(self.document, startnode=pending)
+            transform.apply()
+            self.applied.append((priority, transform_class, pending))

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/components.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/components.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/components.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,54 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Docutils component-related transforms.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import os
+import re
+import time
+from docutils import nodes, utils
+from docutils import ApplicationError, DataError
+from docutils.transforms import Transform, TransformError
+
+
+class Filter(Transform):
+
+    """
+    Include or exclude elements which depend on a specific Docutils component.
+
+    For use with `nodes.pending` elements.  A "pending" element's dictionary
+    attribute ``details`` must contain the keys "component" and "format".  The
+    value of ``details['component']`` must match the type name of the
+    component the elements depend on (e.g. "writer").  The value of
+    ``details['format']`` is the name of a specific format or context of that
+    component (e.g. "html").  If the matching Docutils component supports that
+    format or context, the "pending" element is replaced by the contents of
+    ``details['nodes']`` (a list of nodes); otherwise, the "pending" element
+    is removed.
+
+    For example, the reStructuredText "meta" directive creates a "pending"
+    element containing a "meta" element (in ``pending.details['nodes']``).
+    Only writers (``pending.details['component'] == 'writer'``) supporting the
+    "html" format (``pending.details['format'] == 'html'``) will include the
+    "meta" element; it will be deleted from the output of all other writers.
+    """
+
+    default_priority = 780
+
+    def apply(self):
+        pending = self.startnode
+        component_type = pending.details['component'] # 'reader' or 'writer'
+        format = pending.details['format']
+        component = self.document.transformer.components[component_type]
+        if component.supports(format):
+            pending.parent.replace(pending, pending.details['nodes'])
+        else:
+            pending.parent.remove(pending)

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/frontmatter.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/frontmatter.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/frontmatter.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,399 @@
+# Authors: David Goodger, Ueli Schlaepfer
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Transforms related to the front matter of a document (information
+found before the main text):
+
+- `DocTitle`: Used to transform a lone top level section's title to
+  the document title, and promote a remaining lone top-level section's
+  title to the document subtitle.
+
+- `DocInfo`: Used to transform a bibliographic field list into docinfo
+  elements.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import re
+from docutils import nodes, utils
+from docutils.transforms import TransformError, Transform
+
+
+class DocTitle(Transform):
+
+    """
+    In reStructuredText_, there is no way to specify a document title
+    and subtitle explicitly. Instead, we can supply the document title
+    (and possibly the subtitle as well) implicitly, and use this
+    two-step transform to "raise" or "promote" the title(s) (and their
+    corresponding section contents) to the document level.
+
+    1. If the document contains a single top-level section as its
+       first non-comment element, the top-level section's title
+       becomes the document's title, and the top-level section's
+       contents become the document's immediate contents. The lone
+       top-level section header must be the first non-comment element
+       in the document.
+
+       For example, take this input text::
+
+           =================
+            Top-Level Title
+           =================
+
+           A paragraph.
+
+       Once parsed, it looks like this::
+
+           <document>
+               <section name="top-level title">
+                   <title>
+                       Top-Level Title
+                   <paragraph>
+                       A paragraph.
+
+       After running the DocTitle transform, we have::
+
+           <document name="top-level title">
+               <title>
+                   Top-Level Title
+               <paragraph>
+                   A paragraph.
+
+    2. If step 1 successfully determines the document title, we
+       continue by checking for a subtitle.
+
+       If the lone top-level section itself contains a single
+       second-level section as its first non-comment element, that
+       section's title is promoted to the document's subtitle, and
+       that section's contents become the document's immediate
+       contents. Given this input text::
+
+           =================
+            Top-Level Title
+           =================
+
+           Second-Level Title
+           ~~~~~~~~~~~~~~~~~~
+
+           A paragraph.
+
+       After parsing and running the Section Promotion transform, the
+       result is::
+
+           <document name="top-level title">
+               <title>
+                   Top-Level Title
+               <subtitle name="second-level title">
+                   Second-Level Title
+               <paragraph>
+                   A paragraph.
+
+       (Note that the implicit hyperlink target generated by the
+       "Second-Level Title" is preserved on the "subtitle" element
+       itself.)
+
+    Any comment elements occurring before the document title or
+    subtitle are accumulated and inserted as the first body elements
+    after the title(s).
+    """
+
+    default_priority = 320
+
+    def apply(self):
+        if not getattr(self.document.settings, 'doctitle_xform', 1):
+            return
+        if self.promote_document_title():
+            self.promote_document_subtitle()
+
+    def promote_document_title(self):
+        section, index = self.candidate_index()
+        if index is None:
+            return None
+        document = self.document
+        # Transfer the section's attributes to the document element (at root):
+        document.attributes.update(section.attributes)
+        document[:] = (section[:1]        # section title
+                       + document[:index] # everything that was in the
+                                          # document before the section
+                       + section[1:])     # everything that was in the section
+        return 1
+
+    def promote_document_subtitle(self):
+        subsection, index = self.candidate_index()
+        if index is None:
+            return None
+        subtitle = nodes.subtitle()
+        # Transfer the subsection's attributes to the new subtitle:
+        subtitle.attributes.update(subsection.attributes)
+        # Transfer the contents of the subsection's title to the subtitle:
+        subtitle[:] = subsection[0][:]
+        document = self.document
+        document[:] = (document[:1]       # document title
+                       + [subtitle]
+                       # everything that was before the section:
+                       + document[1:index]
+                       # everything that was in the subsection:
+                       + subsection[1:])
+        return 1
+
+    def candidate_index(self):
+        """
+        Find and return the promotion candidate and its index.
+
+        Return (None, None) if no valid candidate was found.
+        """
+        document = self.document
+        index = document.first_child_not_matching_class(
+              nodes.PreBibliographic)
+        if index is None or len(document) > (index + 1) or \
+              not isinstance(document[index], nodes.section):
+            return None, None
+        else:
+            return document[index], index
+
+
+class DocInfo(Transform):
+
+    """
+    This transform is specific to the reStructuredText_ markup syntax;
+    see "Bibliographic Fields" in the `reStructuredText Markup
+    Specification`_ for a high-level description. This transform
+    should be run *after* the `DocTitle` transform.
+
+    Given a field list as the first non-comment element after the
+    document title and subtitle (if present), registered bibliographic
+    field names are transformed to the corresponding DTD elements,
+    becoming child elements of the "docinfo" element (except for a
+    dedication and/or an abstract, which become "topic" elements after
+    "docinfo").
+
+    For example, given this document fragment after parsing::
+
+        <document>
+            <title>
+                Document Title
+            <field_list>
+                <field>
+                    <field_name>
+                        Author
+                    <field_body>
+                        <paragraph>
+                            A. Name
+                <field>
+                    <field_name>
+                        Status
+                    <field_body>
+                        <paragraph>
+                            $RCSfile: frontmatter.py,v $
+            ...
+
+    After running the bibliographic field list transform, the
+    resulting document tree would look like this::
+
+        <document>
+            <title>
+                Document Title
+            <docinfo>
+                <author>
+                    A. Name
+                <status>
+                    frontmatter.py
+            ...
+
+    The "Status" field contained an expanded RCS keyword, which is
+    normally (but optionally) cleaned up by the transform. The sole
+    contents of the field body must be a paragraph containing an
+    expanded RCS keyword of the form "$keyword: expansion text $". Any
+    RCS keyword can be processed in any bibliographic field. The
+    dollar signs and leading RCS keyword name are removed. Extra
+    processing is done for the following RCS keywords:
+
+    - "RCSfile" expands to the name of the file in the RCS or CVS
+      repository, which is the name of the source file with a ",v"
+      suffix appended. The transform will remove the ",v" suffix.
+
+    - "Date" expands to the format "YYYY/MM/DD hh:mm:ss" (in the UTC
+      time zone). The RCS Keywords transform will extract just the
+      date itself and transform it to an ISO 8601 format date, as in
+      "2000-12-31".
+
+      (Since the source file for this text is itself stored under CVS,
+      we can't show an example of the "Date" RCS keyword because we
+      can't prevent any RCS keywords used in this explanation from
+      being expanded. Only the "RCSfile" keyword is stable; its
+      expansion text changes only if the file name changes.)
+    """
+
+    default_priority = 340
+
+    biblio_nodes = {
+          'author': nodes.author,
+          'authors': nodes.authors,
+          'organization': nodes.organization,
+          'address': nodes.address,
+          'contact': nodes.contact,
+          'version': nodes.version,
+          'revision': nodes.revision,
+          'status': nodes.status,
+          'date': nodes.date,
+          'copyright': nodes.copyright,
+          'dedication': nodes.topic,
+          'abstract': nodes.topic}
+    """Canonical field name (lowcased) to node class name mapping for
+    bibliographic fields (field_list)."""
+
+    def apply(self):
+        if not getattr(self.document.settings, 'docinfo_xform', 1):
+            return
+        document = self.document
+        index = document.first_child_not_matching_class(
+              nodes.PreBibliographic)
+        if index is None:
+            return
+        candidate = document[index]
+        if isinstance(candidate, nodes.field_list):
+            biblioindex = document.first_child_not_matching_class(
+                  nodes.Titular)
+            nodelist = self.extract_bibliographic(candidate)
+            del document[index]         # untransformed field list (candidate)
+            document[biblioindex:biblioindex] = nodelist
+        return
+
+    def extract_bibliographic(self, field_list):
+        docinfo = nodes.docinfo()
+        bibliofields = self.language.bibliographic_fields
+        labels = self.language.labels
+        topics = {'dedication': None, 'abstract': None}
+        for field in field_list:
+            try:
+                name = field[0][0].astext()
+                normedname = nodes.fully_normalize_name(name)
+                if not (len(field) == 2 and bibliofields.has_key(normedname)
+                        and self.check_empty_biblio_field(field, name)):
+                    raise TransformError
+                canonical = bibliofields[normedname]
+                biblioclass = self.biblio_nodes[canonical]
+                if issubclass(biblioclass, nodes.TextElement):
+                    if not self.check_compound_biblio_field(field, name):
+                        raise TransformError
+                    utils.clean_rcs_keywords(
+                          field[1][0], self.rcs_keyword_substitutions)
+                    docinfo.append(biblioclass('', '', *field[1][0]))
+                elif issubclass(biblioclass, nodes.authors):
+                    self.extract_authors(field, name, docinfo)
+                elif issubclass(biblioclass, nodes.topic):
+                    if topics[canonical]:
+                        field[-1] += self.document.reporter.warning(
+                            'There can only be one "%s" field.' % name,
+                            base_node=field)
+                        raise TransformError
+                    title = nodes.title(name, labels[canonical])
+                    topics[canonical] = biblioclass(
+                        '', title, CLASS=canonical, *field[1].children)
+                else:
+                    docinfo.append(biblioclass('', *field[1].children))
+            except TransformError:
+                if len(field[-1]) == 1 \
+                       and isinstance(field[-1][0], nodes.paragraph):
+                    utils.clean_rcs_keywords(
+                        field[-1][0], self.rcs_keyword_substitutions)
+                docinfo.append(field)
+        nodelist = []
+        if len(docinfo) != 0:
+            nodelist.append(docinfo)
+        for name in ('dedication', 'abstract'):
+            if topics[name]:
+                nodelist.append(topics[name])
+        return nodelist
+
+    def check_empty_biblio_field(self, field, name):
+        if len(field[-1]) < 1:
+            field[-1] += self.document.reporter.warning(
+                  'Cannot extract empty bibliographic field "%s".' % name,
+                  base_node=field)
+            return None
+        return 1
+
+    def check_compound_biblio_field(self, field, name):
+        if len(field[-1]) > 1:
+            field[-1] += self.document.reporter.warning(
+                  'Cannot extract compound bibliographic field "%s".' % name,
+                  base_node=field)
+            return None
+        if not isinstance(field[-1][0], nodes.paragraph):
+            field[-1] += self.document.reporter.warning(
+                  'Cannot extract bibliographic field "%s" containing '
+                  'anything other than a single paragraph.' % name,
+                  base_node=field)
+            return None
+        return 1
+
+    rcs_keyword_substitutions = [
+          (re.compile(r'\$' r'Date: (\d\d\d\d)/(\d\d)/(\d\d) [\d:]+ \$',
+                      re.IGNORECASE), r'\1-\2-\3'),
+          (re.compile(r'\$' r'RCSfile: (.+),v \$', re.IGNORECASE), r'\1'),
+          (re.compile(r'\$[a-zA-Z]+: (.+) \$'), r'\1'),]
+
+    def extract_authors(self, field, name, docinfo):
+        try:
+            if len(field[1]) == 1:
+                if isinstance(field[1][0], nodes.paragraph):
+                    authors = self.authors_from_one_paragraph(field)
+                elif isinstance(field[1][0], nodes.bullet_list):
+                    authors = self.authors_from_bullet_list(field)
+                else:
+                    raise TransformError
+            else:
+                authors = self.authors_from_paragraphs(field)
+            authornodes = [nodes.author('', '', *author)
+                           for author in authors if author]
+            if len(authornodes) > 1:
+                docinfo.append(nodes.authors('', *authornodes))
+            elif len(authornodes) == 1:
+                docinfo.append(authornodes[0])
+            else:
+                raise TransformError
+        except TransformError:
+            field[-1] += self.document.reporter.warning(
+                  'Bibliographic field "%s" incompatible with extraction: '
+                  'it must contain either a single paragraph (with authors '
+                  'separated by one of "%s"), multiple paragraphs (one per '
+                  'author), or a bullet list with one paragraph (one author) '
+                  'per item.'
+                  % (name, ''.join(self.language.author_separators)),
+                  base_node=field)
+            raise
+
+    def authors_from_one_paragraph(self, field):
+        text = field[1][0].astext().strip()
+        if not text:
+            raise TransformError
+        for authorsep in self.language.author_separators:
+            authornames = text.split(authorsep)
+            if len(authornames) > 1:
+                break
+        authornames = [author.strip() for author in authornames]
+        authors = [[nodes.Text(author)] for author in authornames if author]
+        return authors
+
+    def authors_from_bullet_list(self, field):
+        authors = []
+        for item in field[1][0]:
+            if len(item) != 1 or not isinstance(item[0], nodes.paragraph):
+                raise TransformError
+            authors.append(item[0].children)
+        if not authors:
+            raise TransformError
+        return authors
+
+    def authors_from_paragraphs(self, field):
+        for item in field[1]:
+            if not isinstance(item, nodes.paragraph):
+                raise TransformError
+        authors = [item.children for item in field[1]]
+        return authors

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/misc.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/misc.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/misc.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,70 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Miscellaneous transforms.
+"""
+
+__docformat__ = 'reStructuredText'
+
+from docutils import nodes
+from docutils.transforms import Transform, TransformError
+
+
+class CallBack(Transform):
+
+    """
+    Inserts a callback into a document.  The callback is called when the
+    transform is applied, which is determined by its priority.
+
+    For use with `nodes.pending` elements.  Requires a ``details['callback']``
+    entry, a bound method or function which takes one parameter: the pending
+    node.  Other data can be stored in the ``details`` attribute or in the
+    object hosting the callback method.
+    """
+
+    default_priority = 990
+
+    def apply(self):
+        pending = self.startnode
+        pending.details['callback'](pending)
+        pending.parent.remove(pending)
+
+
+class ClassAttribute(Transform):
+
+    """
+    Move the "class" attribute specified in the "pending" node into the
+    immediately following non-comment element.
+    """
+
+    default_priority = 210
+
+    def apply(self):
+        pending = self.startnode
+        class_value = pending.details['class']
+        parent = pending.parent
+        child = pending
+        while parent:
+            # Check for appropriate following siblings:
+            for index in range(parent.index(child) + 1, len(parent)):
+                element = parent[index]
+                if (isinstance(element, nodes.Invisible) or
+                    isinstance(element, nodes.system_message)):
+                    continue
+                element.set_class(class_value)
+                pending.parent.remove(pending)
+                return
+            else:
+                # At end of section or container; apply to sibling
+                child = parent
+                parent = parent.parent
+        error = self.document.reporter.error(
+            'No suitable element following "%s" directive'
+            % pending.details['directive'],
+            nodes.literal_block(pending.rawsource, pending.rawsource),
+            line=pending.line)
+        pending.parent.replace(pending, error)

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/parts.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/parts.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/parts.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,170 @@
+# Authors: David Goodger, Ueli Schlaepfer, Dmitry Jemerov
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Transforms related to document parts.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import re
+import sys
+from docutils import nodes, utils
+from docutils.transforms import TransformError, Transform
+
+
+class SectNum(Transform):
+
+    """
+    Automatically assigns numbers to the titles of document sections.
+
+    It is possible to limit the maximum section level for which the numbers
+    are added.  For those sections that are auto-numbered, the "autonum"
+    attribute is set, informing the contents table generator that a different
+    form of the TOC should be used.
+    """
+
+    default_priority = 710
+    """Should be applied before `Contents`."""
+
+    def apply(self):
+        self.maxdepth = self.startnode.details.get('depth', sys.maxint)
+        self.startvalue = self.startnode.details.get('start', 1)
+        self.prefix = self.startnode.details.get('prefix', '')
+        self.suffix = self.startnode.details.get('suffix', '')
+        self.startnode.parent.remove(self.startnode)
+        if self.document.settings.sectnum_xform:
+            self.update_section_numbers(self.document)
+
+    def update_section_numbers(self, node, prefix=(), depth=0):
+        depth += 1
+        if prefix:
+            sectnum = 1
+        else:
+            sectnum = self.startvalue
+        for child in node:
+            if isinstance(child, nodes.section):
+                numbers = prefix + (str(sectnum),)
+                title = child[0]
+                # Use &nbsp; for spacing:
+                generated = nodes.generated(
+                    '', (self.prefix + '.'.join(numbers) + self.suffix
+                         +  u'\u00a0' * 3),
+                    CLASS='sectnum')
+                title.insert(0, generated)
+                title['auto'] = 1
+                if depth < self.maxdepth:
+                    self.update_section_numbers(child, numbers, depth)
+                sectnum += 1
+
+
+class Contents(Transform):
+
+    """
+    This transform generates a table of contents from the entire document tree
+    or from a single branch.  It locates "section" elements and builds them
+    into a nested bullet list, which is placed within a "topic" created by the
+    contents directive.  A title is either explicitly specified, taken from
+    the appropriate language module, or omitted (local table of contents).
+    The depth may be specified.  Two-way references between the table of
+    contents and section titles are generated (requires Writer support).
+
+    This transform requires a startnode, which which contains generation
+    options and provides the location for the generated table of contents (the
+    startnode is replaced by the table of contents "topic").
+    """
+
+    default_priority = 720
+
+    def apply(self):
+        details = self.startnode.details
+        if details.has_key('local'):
+            startnode = self.startnode.parent.parent
+            # @@@ generate an error if the startnode (directive) not at
+            # section/document top-level? Drag it up until it is?
+            while not isinstance(startnode, nodes.Structural):
+                startnode = startnode.parent
+        else:
+            startnode = self.document
+
+        self.toc_id = self.startnode.parent['id']
+        if details.has_key('backlinks'):
+            self.backlinks = details['backlinks']
+        else:
+            self.backlinks = self.document.settings.toc_backlinks
+        contents = self.build_contents(startnode)
+        if len(contents):
+            self.startnode.parent.replace(self.startnode, contents)
+        else:
+            self.startnode.parent.parent.remove(self.startnode.parent)
+
+    def build_contents(self, node, level=0):
+        level += 1
+        sections = []
+        i = len(node) - 1
+        while i >= 0 and isinstance(node[i], nodes.section):
+            sections.append(node[i])
+            i -= 1
+        sections.reverse()
+        entries = []
+        autonum = 0
+        depth = self.startnode.details.get('depth', sys.maxint)
+        for section in sections:
+            title = section[0]
+            auto = title.get('auto')    # May be set by SectNum.
+            entrytext = self.copy_and_filter(title)
+            reference = nodes.reference('', '', refid=section['id'],
+                                        *entrytext)
+            ref_id = self.document.set_id(reference)
+            entry = nodes.paragraph('', '', reference)
+            item = nodes.list_item('', entry)
+            if self.backlinks == 'entry':
+                title['refid'] = ref_id
+            elif self.backlinks == 'top':
+                title['refid'] = self.toc_id
+            if level < depth:
+                subsects = self.build_contents(section, level)
+                item += subsects
+            entries.append(item)
+        if entries:
+            contents = nodes.bullet_list('', *entries)
+            if auto:
+                contents.set_class('auto-toc')
+            return contents
+        else:
+            return []
+
+    def copy_and_filter(self, node):
+        """Return a copy of a title, with references, images, etc. removed."""
+        visitor = ContentsFilter(self.document)
+        node.walkabout(visitor)
+        return visitor.get_entry_text()
+
+
+class ContentsFilter(nodes.TreeCopyVisitor):
+
+    def get_entry_text(self):
+        return self.get_tree_copy().get_children()
+
+    def visit_citation_reference(self, node):
+        raise nodes.SkipNode
+
+    def visit_footnote_reference(self, node):
+        raise nodes.SkipNode
+
+    def visit_image(self, node):
+        if node.hasattr('alt'):
+            self.parent.append(nodes.Text(node['alt']))
+        raise nodes.SkipNode
+
+    def ignore_node_but_process_children(self, node):
+        raise nodes.SkipDeparture
+
+    visit_interpreted = ignore_node_but_process_children
+    visit_problematic = ignore_node_but_process_children
+    visit_reference = ignore_node_but_process_children
+    visit_target = ignore_node_but_process_children

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/peps.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/peps.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/peps.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,306 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Transforms for PEP processing.
+
+- `Headers`: Used to transform a PEP's initial RFC-2822 header.  It remains a
+  field list, but some entries get processed.
+- `Contents`: Auto-inserts a table of contents.
+- `PEPZero`: Special processing for PEP 0.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import os
+import re
+import time
+from docutils import nodes, utils, languages
+from docutils import ApplicationError, DataError
+from docutils.transforms import Transform, TransformError
+from docutils.transforms import parts, references, misc
+
+
+class Headers(Transform):
+
+    """
+    Process fields in a PEP's initial RFC-2822 header.
+    """
+
+    default_priority = 360
+
+    pep_url = 'pep-%04d.html'
+    pep_cvs_url = ('http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/python/'
+                   'python/nondist/peps/pep-%04d.txt')
+    rcs_keyword_substitutions = (
+          (re.compile(r'\$' r'RCSfile: (.+),v \$$', re.IGNORECASE), r'\1'),
+          (re.compile(r'\$[a-zA-Z]+: (.+) \$$'), r'\1'),)
+
+    def apply(self):
+        if not len(self.document):
+            # @@@ replace these DataErrors with proper system messages
+            raise DataError('Document tree is empty.')
+        header = self.document[0]
+        if not isinstance(header, nodes.field_list) or \
+              header.get('class') != 'rfc2822':
+            raise DataError('Document does not begin with an RFC-2822 '
+                            'header; it is not a PEP.')
+        pep = None
+        for field in header:
+            if field[0].astext().lower() == 'pep': # should be the first field
+                value = field[1].astext()
+                try:
+                    pep = int(value)
+                    cvs_url = self.pep_cvs_url % pep
+                except ValueError:
+                    pep = value
+                    cvs_url = None
+                    msg = self.document.reporter.warning(
+                        '"PEP" header must contain an integer; "%s" is an '
+                        'invalid value.' % pep, base_node=field)
+                    msgid = self.document.set_id(msg)
+                    prb = nodes.problematic(value, value or '(none)',
+                                            refid=msgid)
+                    prbid = self.document.set_id(prb)
+                    msg.add_backref(prbid)
+                    if len(field[1]):
+                        field[1][0][:] = [prb]
+                    else:
+                        field[1] += nodes.paragraph('', '', prb)
+                break
+        if pep is None:
+            raise DataError('Document does not contain an RFC-2822 "PEP" '
+                            'header.')
+        if pep == 0:
+            # Special processing for PEP 0.
+            pending = nodes.pending(PEPZero)
+            self.document.insert(1, pending)
+            self.document.note_pending(pending)
+        if len(header) < 2 or header[1][0].astext().lower() != 'title':
+            raise DataError('No title!')
+        for field in header:
+            name = field[0].astext().lower()
+            body = field[1]
+            if len(body) > 1:
+                raise DataError('PEP header field body contains multiple '
+                                'elements:\n%s' % field.pformat(level=1))
+            elif len(body) == 1:
+                if not isinstance(body[0], nodes.paragraph):
+                    raise DataError('PEP header field body may only contain '
+                                    'a single paragraph:\n%s'
+                                    % field.pformat(level=1))
+            elif name == 'last-modified':
+                date = time.strftime(
+                      '%d-%b-%Y',
+                      time.localtime(os.stat(self.document['source'])[8]))
+                if cvs_url:
+                    body += nodes.paragraph(
+                        '', '', nodes.reference('', date, refuri=cvs_url))
+            else:
+                # empty
+                continue
+            para = body[0]
+            if name == 'author':
+                for node in para:
+                    if isinstance(node, nodes.reference):
+                        node.parent.replace(node, mask_email(node))
+            elif name == 'discussions-to':
+                for node in para:
+                    if isinstance(node, nodes.reference):
+                        node.parent.replace(node, mask_email(node, pep))
+            elif name in ('replaces', 'replaced-by', 'requires'):
+                newbody = []
+                space = nodes.Text(' ')
+                for refpep in re.split(',?\s+', body.astext()):
+                    pepno = int(refpep)
+                    newbody.append(nodes.reference(
+                        refpep, refpep,
+                        refuri=(self.document.settings.pep_base_url
+                                + self.pep_url % pepno)))
+                    newbody.append(space)
+                para[:] = newbody[:-1] # drop trailing space
+            elif name == 'last-modified':
+                utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
+                if cvs_url:
+                    date = para.astext()
+                    para[:] = [nodes.reference('', date, refuri=cvs_url)]
+            elif name == 'content-type':
+                pep_type = para.astext()
+                uri = self.document.settings.pep_base_url + self.pep_url % 12
+                para[:] = [nodes.reference('', pep_type, refuri=uri)]
+            elif name == 'version' and len(body):
+                utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
+
+
+class Contents(Transform):
+
+    """
+    Insert an empty table of contents topic and a transform placeholder into
+    the document after the RFC 2822 header.
+    """
+
+    default_priority = 380
+
+    def apply(self):
+        language = languages.get_language(self.document.settings.language_code)
+        name = language.labels['contents']
+        title = nodes.title('', name)
+        topic = nodes.topic('', title, CLASS='contents')
+        name = nodes.fully_normalize_name(name)
+        if not self.document.has_name(name):
+            topic['name'] = name
+        self.document.note_implicit_target(topic)
+        pending = nodes.pending(parts.Contents)
+        topic += pending
+        self.document.insert(1, topic)
+        self.document.note_pending(pending)
+
+
+class TargetNotes(Transform):
+
+    """
+    Locate the "References" section, insert a placeholder for an external
+    target footnote insertion transform at the end, and schedule the
+    transform to run immediately.
+    """
+
+    default_priority = 520
+
+    def apply(self):
+        doc = self.document
+        i = len(doc) - 1
+        refsect = copyright = None
+        while i >= 0 and isinstance(doc[i], nodes.section):
+            title_words = doc[i][0].astext().lower().split()
+            if 'references' in title_words:
+                refsect = doc[i]
+                break
+            elif 'copyright' in title_words:
+                copyright = i
+            i -= 1
+        if not refsect:
+            refsect = nodes.section()
+            refsect += nodes.title('', 'References')
+            doc.set_id(refsect)
+            if copyright:
+                # Put the new "References" section before "Copyright":
+                doc.insert(copyright, refsect)
+            else:
+                # Put the new "References" section at end of doc:
+                doc.append(refsect)
+        pending = nodes.pending(references.TargetNotes)
+        refsect.append(pending)
+        self.document.note_pending(pending, 0)
+        pending = nodes.pending(misc.CallBack,
+                                details={'callback': self.cleanup_callback})
+        refsect.append(pending)
+        self.document.note_pending(pending, 1)
+
+    def cleanup_callback(self, pending):
+        """
+        Remove an empty "References" section.
+
+        Called after the `references.TargetNotes` transform is complete.
+        """
+        if len(pending.parent) == 2:    # <title> and <pending>
+            pending.parent.parent.remove(pending.parent)
+
+
+class PEPZero(Transform):
+
+    """
+    Special processing for PEP 0.
+    """
+
+    default_priority =760
+
+    def apply(self):
+        visitor = PEPZeroSpecial(self.document)
+        self.document.walk(visitor)
+        self.startnode.parent.remove(self.startnode)
+
+
+class PEPZeroSpecial(nodes.SparseNodeVisitor):
+
+    """
+    Perform the special processing needed by PEP 0:
+
+    - Mask email addresses.
+
+    - Link PEP numbers in the second column of 4-column tables to the PEPs
+      themselves.
+    """
+
+    pep_url = Headers.pep_url
+
+    def unknown_visit(self, node):
+        pass
+
+    def visit_reference(self, node):
+        node.parent.replace(node, mask_email(node))
+
+    def visit_field_list(self, node):
+        if node.hasattr('class') and node['class'] == 'rfc2822':
+            raise nodes.SkipNode
+
+    def visit_tgroup(self, node):
+        self.pep_table = node['cols'] == 4
+        self.entry = 0
+
+    def visit_colspec(self, node):
+        self.entry += 1
+        if self.pep_table and self.entry == 2:
+            node['class'] = 'num'
+
+    def visit_row(self, node):
+        self.entry = 0
+
+    def visit_entry(self, node):
+        self.entry += 1
+        if self.pep_table and self.entry == 2 and len(node) == 1:
+            node['class'] = 'num'
+            p = node[0]
+            if isinstance(p, nodes.paragraph) and len(p) == 1:
+                text = p.astext()
+                try:
+                    pep = int(text)
+                    ref = (self.document.settings.pep_base_url
+                           + self.pep_url % pep)
+                    p[0] = nodes.reference(text, text, refuri=ref)
+                except ValueError:
+                    pass
+
+
+non_masked_addresses = ('peps at python.org',
+                        'python-list at python.org',
+                        'python-dev at python.org')
+
+def mask_email(ref, pepno=None):
+    """
+    Mask the email address in `ref` and return a replacement node.
+
+    `ref` is returned unchanged if it contains no email address.
+
+    For email addresses such as "user at host", mask the address as "user at
+    host" (text) to thwart simple email address harvesters (except for those
+    listed in `non_masked_addresses`).  If a PEP number (`pepno`) is given,
+    return a reference including a default email subject.
+    """
+    if ref.hasattr('refuri') and ref['refuri'].startswith('mailto:'):
+        if ref['refuri'][8:] in non_masked_addresses:
+            replacement = ref[0]
+        else:
+            replacement_text = ref.astext().replace('@', '&#32;&#97;t&#32;')
+            replacement = nodes.raw('', replacement_text, format='html')
+        if pepno is None:
+            return replacement
+        else:
+            ref['refuri'] += '?subject=PEP%%20%s' % pepno
+            ref[:] = [replacement]
+            return ref
+    else:
+        return ref

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/references.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/references.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/references.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,788 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Transforms for resolving references.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import re
+from docutils import nodes, utils
+from docutils.transforms import TransformError, Transform
+
+
+indices = xrange(sys.maxint)
+
+
+class ChainedTargets(Transform):
+
+    """
+    Attributes "refuri" and "refname" are migrated from the final direct
+    target up the chain of contiguous adjacent internal targets, using
+    `ChainedTargetResolver`.
+    """
+
+    default_priority = 420
+
+    def apply(self):
+        visitor = ChainedTargetResolver(self.document)
+        self.document.walk(visitor)
+
+
+class ChainedTargetResolver(nodes.SparseNodeVisitor):
+
+    """
+    Copy reference attributes up the length of a hyperlink target chain.
+
+    "Chained targets" are multiple adjacent internal hyperlink targets which
+    "point to" an external or indirect target.  After the transform, all
+    chained targets will effectively point to the same place.
+
+    Given the following ``document`` as input::
+
+        <document>
+            <target id="a" name="a">
+            <target id="b" name="b">
+            <target id="c" name="c" refuri="http://chained.external.targets">
+            <target id="d" name="d">
+            <paragraph>
+                I'm known as "d".
+            <target id="e" name="e">
+            <target id="id1">
+            <target id="f" name="f" refname="d">
+
+    ``ChainedTargetResolver(document).walk()`` will transform the above into::
+
+        <document>
+            <target id="a" name="a" refuri="http://chained.external.targets">
+            <target id="b" name="b" refuri="http://chained.external.targets">
+            <target id="c" name="c" refuri="http://chained.external.targets">
+            <target id="d" name="d">
+            <paragraph>
+                I'm known as "d".
+            <target id="e" name="e" refname="d">
+            <target id="id1" refname="d">
+            <target id="f" name="f" refname="d">
+    """
+
+    def unknown_visit(self, node):
+        pass
+
+    def visit_target(self, node):
+        if node.hasattr('refuri'):
+            attname = 'refuri'
+            call_if_named = self.document.note_external_target
+        elif node.hasattr('refname'):
+            attname = 'refname'
+            call_if_named = self.document.note_indirect_target
+        elif node.hasattr('refid'):
+            attname = 'refid'
+            call_if_named = None
+        else:
+            return
+        attval = node[attname]
+        index = node.parent.index(node)
+        for i in range(index - 1, -1, -1):
+            sibling = node.parent[i]
+            if not isinstance(sibling, nodes.target) \
+                  or sibling.hasattr('refuri') \
+                  or sibling.hasattr('refname') \
+                  or sibling.hasattr('refid'):
+                break
+            sibling[attname] = attval
+            if sibling.hasattr('name') and call_if_named:
+                call_if_named(sibling)
+
+
+class AnonymousHyperlinks(Transform):
+
+    """
+    Link anonymous references to targets.  Given::
+
+        <paragraph>
+            <reference anonymous="1">
+                internal
+            <reference anonymous="1">
+                external
+        <target anonymous="1" id="id1">
+        <target anonymous="1" id="id2" refuri="http://external">
+
+    Corresponding references are linked via "refid" or resolved via "refuri"::
+
+        <paragraph>
+            <reference anonymous="1" refid="id1">
+                text
+            <reference anonymous="1" refuri="http://external">
+                external
+        <target anonymous="1" id="id1">
+        <target anonymous="1" id="id2" refuri="http://external">
+    """
+
+    default_priority = 440
+
+    def apply(self):
+        if len(self.document.anonymous_refs) \
+              != len(self.document.anonymous_targets):
+            msg = self.document.reporter.error(
+                  'Anonymous hyperlink mismatch: %s references but %s '
+                  'targets.\nSee "backrefs" attribute for IDs.'
+                  % (len(self.document.anonymous_refs),
+                     len(self.document.anonymous_targets)))
+            msgid = self.document.set_id(msg)
+            for ref in self.document.anonymous_refs:
+                prb = nodes.problematic(
+                      ref.rawsource, ref.rawsource, refid=msgid)
+                prbid = self.document.set_id(prb)
+                msg.add_backref(prbid)
+                ref.parent.replace(ref, prb)
+            return
+        for ref, target in zip(self.document.anonymous_refs,
+                               self.document.anonymous_targets):
+            if target.hasattr('refuri'):
+                ref['refuri'] = target['refuri']
+                ref.resolved = 1
+            else:
+                ref['refid'] = target['id']
+                self.document.note_refid(ref)
+            target.referenced = 1
+
+
+class IndirectHyperlinks(Transform):
+
+    """
+    a) Indirect external references::
+
+           <paragraph>
+               <reference refname="indirect external">
+                   indirect external
+           <target id="id1" name="direct external"
+               refuri="http://indirect">
+           <target id="id2" name="indirect external"
+               refname="direct external">
+
+       The "refuri" attribute is migrated back to all indirect targets
+       from the final direct target (i.e. a target not referring to
+       another indirect target)::
+
+           <paragraph>
+               <reference refname="indirect external">
+                   indirect external
+           <target id="id1" name="direct external"
+               refuri="http://indirect">
+           <target id="id2" name="indirect external"
+               refuri="http://indirect">
+
+       Once the attribute is migrated, the preexisting "refname" attribute
+       is dropped.
+
+    b) Indirect internal references::
+
+           <target id="id1" name="final target">
+           <paragraph>
+               <reference refname="indirect internal">
+                   indirect internal
+           <target id="id2" name="indirect internal 2"
+               refname="final target">
+           <target id="id3" name="indirect internal"
+               refname="indirect internal 2">
+
+       Targets which indirectly refer to an internal target become one-hop
+       indirect (their "refid" attributes are directly set to the internal
+       target's "id"). References which indirectly refer to an internal
+       target become direct internal references::
+
+           <target id="id1" name="final target">
+           <paragraph>
+               <reference refid="id1">
+                   indirect internal
+           <target id="id2" name="indirect internal 2" refid="id1">
+           <target id="id3" name="indirect internal" refid="id1">
+    """
+
+    default_priority = 460
+
+    def apply(self):
+        for target in self.document.indirect_targets:
+            if not target.resolved:
+                self.resolve_indirect_target(target)
+            self.resolve_indirect_references(target)
+
+    def resolve_indirect_target(self, target):
+        refname = target['refname']
+        reftarget_id = self.document.nameids.get(refname)
+        if not reftarget_id:
+            # Check the unknown_reference_resolvers
+            for resolver_function in (self.document.transformer
+                                      .unknown_reference_resolvers):
+                if resolver_function(target):
+                    break
+            else:
+                self.nonexistent_indirect_target(target)
+            return
+        reftarget = self.document.ids[reftarget_id]
+        if isinstance(reftarget, nodes.target) \
+              and not reftarget.resolved and reftarget.hasattr('refname'):
+            if hasattr(target, 'multiply_indirect'):
+                #and target.multiply_indirect):
+                #del target.multiply_indirect
+                self.circular_indirect_reference(target)
+                return
+            target.multiply_indirect = 1
+            self.resolve_indirect_target(reftarget) # multiply indirect
+            del target.multiply_indirect
+        if reftarget.hasattr('refuri'):
+            target['refuri'] = reftarget['refuri']
+            if target.hasattr('name'):
+                self.document.note_external_target(target)
+        elif reftarget.hasattr('refid'):
+            target['refid'] = reftarget['refid']
+            self.document.note_refid(target)
+        else:
+            try:
+                target['refid'] = reftarget['id']
+                self.document.note_refid(target)
+            except KeyError:
+                self.nonexistent_indirect_target(target)
+                return
+        del target['refname']
+        target.resolved = 1
+        reftarget.referenced = 1
+
+    def nonexistent_indirect_target(self, target):
+        if self.document.nameids.has_key(target['refname']):
+            self.indirect_target_error(target, 'which is a duplicate, and '
+                                       'cannot be used as a unique reference')
+        else:
+            self.indirect_target_error(target, 'which does not exist')
+
+    def circular_indirect_reference(self, target):
+        self.indirect_target_error(target, 'forming a circular reference')
+
+    def indirect_target_error(self, target, explanation):
+        naming = ''
+        if target.hasattr('name'):
+            naming = '"%s" ' % target['name']
+            reflist = self.document.refnames.get(target['name'], [])
+        else:
+            reflist = self.document.refids.get(target['id'], [])
+        naming += '(id="%s")' % target['id']
+        msg = self.document.reporter.error(
+              'Indirect hyperlink target %s refers to target "%s", %s.'
+              % (naming, target['refname'], explanation),
+              base_node=target)
+        msgid = self.document.set_id(msg)
+        for ref in reflist:
+            prb = nodes.problematic(
+                  ref.rawsource, ref.rawsource, refid=msgid)
+            prbid = self.document.set_id(prb)
+            msg.add_backref(prbid)
+            ref.parent.replace(ref, prb)
+        target.resolved = 1
+
+    def resolve_indirect_references(self, target):
+        if target.hasattr('refid'):
+            attname = 'refid'
+            call_if_named = 0
+            call_method = self.document.note_refid
+        elif target.hasattr('refuri'):
+            attname = 'refuri'
+            call_if_named = 1
+            call_method = self.document.note_external_target
+        else:
+            return
+        attval = target[attname]
+        if target.hasattr('name'):
+            name = target['name']
+            try:
+                reflist = self.document.refnames[name]
+            except KeyError, instance:
+                if target.referenced:
+                    return
+                msg = self.document.reporter.info(
+                      'Indirect hyperlink target "%s" is not referenced.'
+                      % name, base_node=target)
+                target.referenced = 1
+                return
+            delatt = 'refname'
+        else:
+            id = target['id']
+            try:
+                reflist = self.document.refids[id]
+            except KeyError, instance:
+                if target.referenced:
+                    return
+                msg = self.document.reporter.info(
+                      'Indirect hyperlink target id="%s" is not referenced.'
+                      % id, base_node=target)
+                target.referenced = 1
+                return
+            delatt = 'refid'
+        for ref in reflist:
+            if ref.resolved:
+                continue
+            del ref[delatt]
+            ref[attname] = attval
+            if not call_if_named or ref.hasattr('name'):
+                call_method(ref)
+            ref.resolved = 1
+            if isinstance(ref, nodes.target):
+                self.resolve_indirect_references(ref)
+        target.referenced = 1
+
+
+class ExternalTargets(Transform):
+
+    """
+    Given::
+
+        <paragraph>
+            <reference refname="direct external">
+                direct external
+        <target id="id1" name="direct external" refuri="http://direct">
+
+    The "refname" attribute is replaced by the direct "refuri" attribute::
+
+        <paragraph>
+            <reference refuri="http://direct">
+                direct external
+        <target id="id1" name="direct external" refuri="http://direct">
+    """
+
+    default_priority = 640
+
+    def apply(self):
+        for target in self.document.external_targets:
+            if target.hasattr('refuri') and target.hasattr('name'):
+                name = target['name']
+                refuri = target['refuri']
+                try:
+                    reflist = self.document.refnames[name]
+                except KeyError, instance:
+                    # @@@ First clause correct???
+                    if not isinstance(target, nodes.target) or target.referenced:
+                        continue
+                    msg = self.document.reporter.info(
+                          'External hyperlink target "%s" is not referenced.'
+                          % name, base_node=target)
+                    target.referenced = 1
+                    continue
+                for ref in reflist:
+                    if ref.resolved:
+                        continue
+                    del ref['refname']
+                    ref['refuri'] = refuri
+                    ref.resolved = 1
+                target.referenced = 1
+
+
+class InternalTargets(Transform):
+
+    """
+    Given::
+
+        <paragraph>
+            <reference refname="direct internal">
+                direct internal
+        <target id="id1" name="direct internal">
+
+    The "refname" attribute is replaced by "refid" linking to the target's
+    "id"::
+
+        <paragraph>
+            <reference refid="id1">
+                direct internal
+        <target id="id1" name="direct internal">
+    """
+
+    default_priority = 660
+
+    def apply(self):
+        for target in self.document.internal_targets:
+            if target.hasattr('refuri') or target.hasattr('refid') \
+                  or not target.hasattr('name'):
+                continue
+            name = target['name']
+            refid = target['id']
+            try:
+                reflist = self.document.refnames[name]
+            except KeyError, instance:
+                if target.referenced:
+                    continue
+                msg = self.document.reporter.info(
+                      'Internal hyperlink target "%s" is not referenced.'
+                      % name, base_node=target)
+                target.referenced = 1
+                continue
+            for ref in reflist:
+                if ref.resolved:
+                    continue
+                del ref['refname']
+                ref['refid'] = refid
+                ref.resolved = 1
+            target.referenced = 1
+
+
+class Footnotes(Transform):
+
+    """
+    Assign numbers to autonumbered footnotes, and resolve links to footnotes,
+    citations, and their references.
+
+    Given the following ``document`` as input::
+
+        <document>
+            <paragraph>
+                A labeled autonumbered footnote referece:
+                <footnote_reference auto="1" id="id1" refname="footnote">
+            <paragraph>
+                An unlabeled autonumbered footnote referece:
+                <footnote_reference auto="1" id="id2">
+            <footnote auto="1" id="id3">
+                <paragraph>
+                    Unlabeled autonumbered footnote.
+            <footnote auto="1" id="footnote" name="footnote">
+                <paragraph>
+                    Labeled autonumbered footnote.
+
+    Auto-numbered footnotes have attribute ``auto="1"`` and no label.
+    Auto-numbered footnote_references have no reference text (they're
+    empty elements). When resolving the numbering, a ``label`` element
+    is added to the beginning of the ``footnote``, and reference text
+    to the ``footnote_reference``.
+
+    The transformed result will be::
+
+        <document>
+            <paragraph>
+                A labeled autonumbered footnote referece:
+                <footnote_reference auto="1" id="id1" refid="footnote">
+                    2
+            <paragraph>
+                An unlabeled autonumbered footnote referece:
+                <footnote_reference auto="1" id="id2" refid="id3">
+                    1
+            <footnote auto="1" id="id3" backrefs="id2">
+                <label>
+                    1
+                <paragraph>
+                    Unlabeled autonumbered footnote.
+            <footnote auto="1" id="footnote" name="footnote" backrefs="id1">
+                <label>
+                    2
+                <paragraph>
+                    Labeled autonumbered footnote.
+
+    Note that the footnotes are not in the same order as the references.
+
+    The labels and reference text are added to the auto-numbered ``footnote``
+    and ``footnote_reference`` elements.  Footnote elements are backlinked to
+    their references via "refids" attributes.  References are assigned "id"
+    and "refid" attributes.
+
+    After adding labels and reference text, the "auto" attributes can be
+    ignored.
+    """
+
+    default_priority = 620
+
+    autofootnote_labels = None
+    """Keep track of unlabeled autonumbered footnotes."""
+
+    symbols = [
+          # Entries 1-4 and 6 below are from section 12.51 of
+          # The Chicago Manual of Style, 14th edition.
+          '*',                          # asterisk/star
+          u'\u2020',                    # dagger &dagger;
+          u'\u2021',                    # double dagger &Dagger;
+          u'\u00A7',                    # section mark &sect;
+          u'\u00B6',                    # paragraph mark (pilcrow) &para;
+                                        # (parallels ['||'] in CMoS)
+          '#',                          # number sign
+          # The entries below were chosen arbitrarily.
+          u'\u2660',                    # spade suit &spades;
+          u'\u2665',                    # heart suit &hearts;
+          u'\u2666',                    # diamond suit &diams;
+          u'\u2663',                    # club suit &clubs;
+          ]
+
+    def apply(self):
+        self.autofootnote_labels = []
+        startnum = self.document.autofootnote_start
+        self.document.autofootnote_start = self.number_footnotes(startnum)
+        self.number_footnote_references(startnum)
+        self.symbolize_footnotes()
+        self.resolve_footnotes_and_citations()
+
+    def number_footnotes(self, startnum):
+        """
+        Assign numbers to autonumbered footnotes.
+
+        For labeled autonumbered footnotes, copy the number over to
+        corresponding footnote references.
+        """
+        for footnote in self.document.autofootnotes:
+            while 1:
+                label = str(startnum)
+                startnum += 1
+                if not self.document.nameids.has_key(label):
+                    break
+            footnote.insert(0, nodes.label('', label))
+            if footnote.hasattr('dupname'):
+                continue
+            if footnote.hasattr('name'):
+                name = footnote['name']
+                for ref in self.document.footnote_refs.get(name, []):
+                    ref += nodes.Text(label)
+                    ref.delattr('refname')
+                    ref['refid'] = footnote['id']
+                    footnote.add_backref(ref['id'])
+                    self.document.note_refid(ref)
+                    ref.resolved = 1
+            else:
+                footnote['name'] = label
+                self.document.note_explicit_target(footnote, footnote)
+                self.autofootnote_labels.append(label)
+        return startnum
+
+    def number_footnote_references(self, startnum):
+        """Assign numbers to autonumbered footnote references."""
+        i = 0
+        for ref in self.document.autofootnote_refs:
+            if ref.resolved or ref.hasattr('refid'):
+                continue
+            try:
+                label = self.autofootnote_labels[i]
+            except IndexError:
+                msg = self.document.reporter.error(
+                      'Too many autonumbered footnote references: only %s '
+                      'corresponding footnotes available.'
+                      % len(self.autofootnote_labels), base_node=ref)
+                msgid = self.document.set_id(msg)
+                for ref in self.document.autofootnote_refs[i:]:
+                    if ref.resolved or ref.hasattr('refname'):
+                        continue
+                    prb = nodes.problematic(
+                          ref.rawsource, ref.rawsource, refid=msgid)
+                    prbid = self.document.set_id(prb)
+                    msg.add_backref(prbid)
+                    ref.parent.replace(ref, prb)
+                break
+            ref += nodes.Text(label)
+            id = self.document.nameids[label]
+            footnote = self.document.ids[id]
+            ref['refid'] = id
+            self.document.note_refid(ref)
+            footnote.add_backref(ref['id'])
+            ref.resolved = 1
+            i += 1
+
+    def symbolize_footnotes(self):
+        """Add symbols indexes to "[*]"-style footnotes and references."""
+        labels = []
+        for footnote in self.document.symbol_footnotes:
+            reps, index = divmod(self.document.symbol_footnote_start,
+                                 len(self.symbols))
+            labeltext = self.symbols[index] * (reps + 1)
+            labels.append(labeltext)
+            footnote.insert(0, nodes.label('', labeltext))
+            self.document.symbol_footnote_start += 1
+            self.document.set_id(footnote)
+        i = 0
+        for ref in self.document.symbol_footnote_refs:
+            try:
+                ref += nodes.Text(labels[i])
+            except IndexError:
+                msg = self.document.reporter.error(
+                      'Too many symbol footnote references: only %s '
+                      'corresponding footnotes available.' % len(labels),
+                      base_node=ref)
+                msgid = self.document.set_id(msg)
+                for ref in self.document.symbol_footnote_refs[i:]:
+                    if ref.resolved or ref.hasattr('refid'):
+                        continue
+                    prb = nodes.problematic(
+                          ref.rawsource, ref.rawsource, refid=msgid)
+                    prbid = self.document.set_id(prb)
+                    msg.add_backref(prbid)
+                    ref.parent.replace(ref, prb)
+                break
+            footnote = self.document.symbol_footnotes[i]
+            ref['refid'] = footnote['id']
+            self.document.note_refid(ref)
+            footnote.add_backref(ref['id'])
+            i += 1
+
+    def resolve_footnotes_and_citations(self):
+        """
+        Link manually-labeled footnotes and citations to/from their
+        references.
+        """
+        for footnote in self.document.footnotes:
+            label = footnote['name']
+            if self.document.footnote_refs.has_key(label):
+                reflist = self.document.footnote_refs[label]
+                self.resolve_references(footnote, reflist)
+        for citation in self.document.citations:
+            label = citation['name']
+            if self.document.citation_refs.has_key(label):
+                reflist = self.document.citation_refs[label]
+                self.resolve_references(citation, reflist)
+
+    def resolve_references(self, note, reflist):
+        id = note['id']
+        for ref in reflist:
+            if ref.resolved:
+                continue
+            ref.delattr('refname')
+            ref['refid'] = id
+            note.add_backref(ref['id'])
+            ref.resolved = 1
+        note.resolved = 1
+
+
+class Substitutions(Transform):
+
+    """
+    Given the following ``document`` as input::
+
+        <document>
+            <paragraph>
+                The
+                <substitution_reference refname="biohazard">
+                    biohazard
+                 symbol is deservedly scary-looking.
+            <substitution_definition name="biohazard">
+                <image alt="biohazard" uri="biohazard.png">
+
+    The ``substitution_reference`` will simply be replaced by the
+    contents of the corresponding ``substitution_definition``.
+
+    The transformed result will be::
+
+        <document>
+            <paragraph>
+                The
+                <image alt="biohazard" uri="biohazard.png">
+                 symbol is deservedly scary-looking.
+            <substitution_definition name="biohazard">
+                <image alt="biohazard" uri="biohazard.png">
+    """
+
+    default_priority = 220
+    """The Substitutions transform has to be applied very early, before
+    `docutils.tranforms.frontmatter.DocTitle` and others."""
+
+    def apply(self):
+        defs = self.document.substitution_defs
+        normed = self.document.substitution_names
+        for refname, refs in self.document.substitution_refs.items():
+            for ref in refs:
+                key = None
+                if defs.has_key(refname):
+                    key = refname
+                else:
+                    normed_name = refname.lower()
+                    if normed.has_key(normed_name):
+                        key = normed[normed_name]
+                if key is None:
+                    msg = self.document.reporter.error(
+                          'Undefined substitution referenced: "%s".'
+                          % refname, base_node=ref)
+                    msgid = self.document.set_id(msg)
+                    prb = nodes.problematic(
+                          ref.rawsource, ref.rawsource, refid=msgid)
+                    prbid = self.document.set_id(prb)
+                    msg.add_backref(prbid)
+                    ref.parent.replace(ref, prb)
+                else:
+                    subdef = defs[key]
+                    parent = ref.parent
+                    index = parent.index(ref)
+                    if  (subdef.attributes.has_key('ltrim')
+                         or subdef.attributes.has_key('trim')):
+                        if index > 0 and isinstance(parent[index - 1],
+                                                    nodes.Text):
+                            parent.replace(parent[index - 1],
+                                           parent[index - 1].rstrip())
+                    if  (subdef.attributes.has_key('rtrim')
+                         or subdef.attributes.has_key('trim')):
+                        if  (len(parent) > index + 1
+                             and isinstance(parent[index + 1], nodes.Text)):
+                            parent.replace(parent[index + 1],
+                                           parent[index + 1].lstrip())
+                    parent.replace(ref, subdef.get_children())
+        self.document.substitution_refs = None  # release replaced references
+
+
+class TargetNotes(Transform):
+
+    """
+    Creates a footnote for each external target in the text, and corresponding
+    footnote references after each reference.
+    """
+
+    default_priority = 540
+    """The TargetNotes transform has to be applied after `IndirectHyperlinks`
+    but before `Footnotes`."""
+
+    def apply(self):
+        notes = {}
+        nodelist = []
+        for target in self.document.external_targets:
+            name = target.get('name')
+            if not name:
+                print >>sys.stderr, 'no name on target: %r' % target
+                continue
+            refs = self.document.refnames.get(name, [])
+            if not refs:
+                continue
+            footnote = self.make_target_footnote(target, refs, notes)
+            if not notes.has_key(target['refuri']):
+                notes[target['refuri']] = footnote
+                nodelist.append(footnote)
+        if len(self.document.anonymous_targets) \
+               == len(self.document.anonymous_refs):
+            for target, ref in zip(self.document.anonymous_targets,
+                                   self.document.anonymous_refs):
+                if target.hasattr('refuri'):
+                    footnote = self.make_target_footnote(target, [ref], notes)
+                    if not notes.has_key(target['refuri']):
+                        notes[target['refuri']] = footnote
+                        nodelist.append(footnote)
+        self.startnode.parent.replace(self.startnode, nodelist)
+
+    def make_target_footnote(self, target, refs, notes):
+        refuri = target['refuri']
+        if notes.has_key(refuri):  # duplicate?
+            footnote = notes[refuri]
+            footnote_name = footnote['name']
+        else:                           # original
+            footnote = nodes.footnote()
+            footnote_id = self.document.set_id(footnote)
+            # Use a colon; they can't be produced inside names by the parser:
+            footnote_name = 'target_note: ' + footnote_id
+            footnote['auto'] = 1
+            footnote['name'] = footnote_name
+            footnote_paragraph = nodes.paragraph()
+            footnote_paragraph += nodes.reference('', refuri, refuri=refuri)
+            footnote += footnote_paragraph
+            self.document.note_autofootnote(footnote)
+            self.document.note_explicit_target(footnote, footnote)
+        for ref in refs:
+            if isinstance(ref, nodes.target):
+                continue
+            refnode = nodes.footnote_reference(
+                refname=footnote_name, auto=1)
+            self.document.note_autofootnote_ref(refnode)
+            self.document.note_footnote_ref(refnode)
+            index = ref.parent.index(ref) + 1
+            reflist = [refnode]
+            if not utils.get_trim_footnote_ref_space(self.document.settings):
+                reflist.insert(0, nodes.Text(' '))
+            ref.parent.insert(index, reflist)
+        return footnote

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/universal.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/universal.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/transforms/universal.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,291 @@
+# Authors: David Goodger, Ueli Schlaepfer
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Transforms needed by most or all documents:
+
+- `Decorations`: Generate a document's header & footer.
+- `Messages`: Placement of system messages stored in
+  `nodes.document.transform_messages`.
+- `TestMessages`: Like `Messages`, used on test runs.
+- `FinalReferences`: Resolve remaining references.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import re
+import sys
+import time
+from docutils import nodes, utils
+from docutils.transforms import TransformError, Transform
+
+
+class Decorations(Transform):
+
+    """
+    Populate a document's decoration element (header, footer).
+    """
+
+    default_priority = 820
+
+    def apply(self):
+        header = self.generate_header()
+        footer = self.generate_footer()
+        if header or footer:
+            decoration = nodes.decoration()
+            decoration += header
+            decoration += footer
+            document = self.document
+            index = document.first_child_not_matching_class(
+                nodes.PreDecorative)
+            if index is None:
+                document += decoration
+            else:
+                document[index:index] = [decoration]
+
+    def generate_header(self):
+        return None
+
+    def generate_footer(self):
+        # @@@ Text is hard-coded for now.
+        # Should be made dynamic (language-dependent).
+        settings = self.document.settings
+        if settings.generator or settings.datestamp or settings.source_link \
+               or settings.source_url:
+            text = []
+            if settings.source_link and settings._source \
+                   or settings.source_url:
+                if settings.source_url:
+                    source = settings.source_url
+                else:
+                    source = utils.relative_path(settings._destination,
+                                                 settings._source)
+                text.extend([
+                    nodes.reference('', 'View document source',
+                                    refuri=source),
+                    nodes.Text('.\n')])
+            if settings.datestamp:
+                datestamp = time.strftime(settings.datestamp, time.gmtime())
+                text.append(nodes.Text('Generated on: ' + datestamp + '.\n'))
+            if settings.generator:
+                text.extend([
+                    nodes.Text('Generated by '),
+                    nodes.reference('', 'Docutils', refuri=
+                                    'http://docutils.sourceforge.net/'),
+                    nodes.Text(' from '),
+                    nodes.reference('', 'reStructuredText', refuri='http://'
+                                    'docutils.sourceforge.net/rst.html'),
+                    nodes.Text(' source.\n')])
+            footer = nodes.footer()
+            footer += nodes.paragraph('', '', *text)
+            return footer
+        else:
+            return None
+
+
+class Messages(Transform):
+
+    """
+    Place any system messages generated after parsing into a dedicated section
+    of the document.
+    """
+
+    default_priority = 860
+
+    def apply(self):
+        unfiltered = self.document.transform_messages
+        threshold = self.document.reporter['writer'].report_level
+        messages = []
+        for msg in unfiltered:
+            if msg['level'] >= threshold and not msg.parent:
+                messages.append(msg)
+        if messages:
+            section = nodes.section(CLASS='system-messages')
+            # @@@ get this from the language module?
+            section += nodes.title('', 'Docutils System Messages')
+            section += messages
+            self.document.transform_messages[:] = []
+            self.document += section
+
+
+class FilterMessages(Transform):
+
+    """
+    Remove system messages below verbosity threshold.
+    """
+
+    default_priority = 870
+
+    def apply(self):
+        visitor = SystemMessageFilterVisitor(self.document)
+        self.document.walk(visitor)
+
+
+class SystemMessageFilterVisitor(nodes.SparseNodeVisitor):
+
+    def unknown_visit(self, node):
+        pass
+
+    def visit_system_message(self, node):
+        if node['level'] < self.document.reporter['writer'].report_level:
+            node.parent.remove(node)
+
+
+class TestMessages(Transform):
+
+    """
+    Append all post-parse system messages to the end of the document.
+    """
+
+    default_priority = 890
+
+    def apply(self):
+        for msg in self.document.transform_messages:
+            if not msg.parent:
+                self.document += msg
+
+
+class FinalChecks(Transform):
+
+    """
+    Perform last-minute checks and transforms.
+
+    - Check for dangling references (incl. footnote & citation).
+    - Check for illegal transitions, move transitions.
+    """
+
+    default_priority = 840
+
+    def apply(self):
+        visitor = FinalCheckVisitor(
+            self.document,
+            self.document.transformer.unknown_reference_resolvers)
+        self.document.walk(visitor)
+        if self.document.settings.expose_internals:
+            visitor = InternalAttributeExposer(self.document)
+            self.document.walk(visitor)
+
+
+class FinalCheckVisitor(nodes.SparseNodeVisitor):
+    
+    def __init__(self, document, unknown_reference_resolvers):
+        nodes.SparseNodeVisitor.__init__(self, document)
+        self.document = document
+        self.unknown_reference_resolvers = unknown_reference_resolvers
+
+    def unknown_visit(self, node):
+        pass
+
+    def visit_reference(self, node):
+        if node.resolved or not node.hasattr('refname'):
+            return
+        refname = node['refname']
+        id = self.document.nameids.get(refname)
+        if id is None:
+            for resolver_function in self.unknown_reference_resolvers:
+                if resolver_function(node):
+                    break
+            else:
+                if self.document.nameids.has_key(refname):
+                    msg = self.document.reporter.error(
+                        'Duplicate target name, cannot be used as a unique '
+                        'reference: "%s".' % (node['refname']), base_node=node)
+                else:
+                    msg = self.document.reporter.error(
+                        'Unknown target name: "%s".' % (node['refname']),
+                        base_node=node)
+                msgid = self.document.set_id(msg)
+                prb = nodes.problematic(
+                      node.rawsource, node.rawsource, refid=msgid)
+                prbid = self.document.set_id(prb)
+                msg.add_backref(prbid)
+                node.parent.replace(node, prb)
+        else:
+            del node['refname']
+            node['refid'] = id
+            self.document.ids[id].referenced = 1
+            node.resolved = 1
+
+    visit_footnote_reference = visit_citation_reference = visit_reference
+
+    def visit_transition(self, node):
+        """
+        Move transitions at the end of sections up the tree.  Complain
+        on transitions after a title, at the beginning or end of the
+        document, and after another transition.
+
+        For example, transform this::
+
+            <section>
+                ...
+                <transition>
+            <section>
+                ...
+
+        into this::
+
+            <section>
+                ...
+            <transition>
+            <section>
+                ...
+        """
+        index = node.parent.index(node)
+        error = None
+        if (index == 0 or
+            isinstance(node.parent[0], nodes.title) and
+            (index == 1 or
+             isinstance(node.parent[1], nodes.subtitle) and
+             index == 2)):
+            assert (isinstance(node.parent, nodes.document) or
+                    isinstance(node.parent, nodes.section))
+            error = self.document.reporter.error(
+                'Document or section may not begin with a transition.',
+                line=node.line)
+        elif isinstance(node.parent[index - 1], nodes.transition):
+            error = self.document.reporter.error(
+                'At least one body element must separate transitions; '
+                'adjacent transitions are not allowed.', line=node.line)
+        if error:
+            # Insert before node and update index.
+            node.parent.insert(index, error)
+            index += 1
+        assert index < len(node.parent)
+        if index != len(node.parent) - 1:
+            # No need to move the node.
+            return
+        # Node behind which the transition is to be moved.
+        sibling = node
+        # While sibling is the last node of its parent.
+        while index == len(sibling.parent) - 1:
+            sibling = sibling.parent
+            # If sibling is the whole document (i.e. it has no parent).
+            if sibling.parent is None:
+                # Transition at the end of document.  Do not move the
+                # transition up, and place an error behind.
+                error = self.document.reporter.error(
+                    'Document may not end with a transition.',
+                    line=node.line)
+                node.parent.insert(node.parent.index(node) + 1, error)
+                return
+            index = sibling.parent.index(sibling)
+        # Remove the original transition node.
+        node.parent.remove(node)
+        # Insert the transition after the sibling.
+        sibling.parent.insert(index + 1, node)
+
+
+class InternalAttributeExposer(nodes.GenericNodeVisitor):
+
+    def __init__(self, document):
+        nodes.GenericNodeVisitor.__init__(self, document)
+        self.internal_attributes = document.settings.expose_internals
+
+    def default_visit(self, node):
+        for att in self.internal_attributes:
+            value = getattr(node, att, None)
+            if value is not None:
+                node['internal:' + att] = value

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/urischemes.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/urischemes.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/urischemes.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,122 @@
+"""
+`schemes` is a dictionary with lowercase URI addressing schemes as
+keys and descriptions as values. It was compiled from the index at
+http://www.iana.org/assignments/uri-schemes (revised 2003-11-26)
+and an older list at http://www.w3.org/Addressing/schemes.html.
+"""
+
+# Many values are blank and should be filled in with useful descriptions.
+
+schemes = {
+      'about': 'provides information on Navigator',
+      'acap': 'Application Configuration Access Protocol',
+      'addbook': "To add vCard entries to Communicator's Address Book",
+      'afp': 'Apple Filing Protocol',
+      'afs': 'Andrew File System global file names',
+      'aim': 'AOL Instant Messenger',
+      'callto': 'for NetMeeting links',
+      'castanet': 'Castanet Tuner URLs for Netcaster',
+      'chttp': 'cached HTTP supported by RealPlayer',
+      'cid': 'content identifier',
+      'data': ('allows inclusion of small data items as "immediate" data; '
+               'RFC 2397'),
+      'dav': 'Distributed Authoring and Versioning Protocol; RFC 2518',
+      'dns': 'Domain Name System resources',
+      'eid': ('External ID; non-URL data; general escape mechanism to allow '
+              'access to information for applications that are too '
+              'specialized to justify their own schemes'),
+      'fax': ('a connection to a terminal that can handle telefaxes '
+              '(facsimiles); RFC 2806'),
+      'feed' : 'NetNewsWire feed',
+      'file': 'Host-specific file names',
+      'finger': '',
+      'freenet': '',
+      'ftp': 'File Transfer Protocol',
+      'go': 'go; RFC3368',
+      'gopher': 'The Gopher Protocol',
+      'gsm-sms': ('Global System for Mobile Communications Short Message '
+                  'Service'),
+      'h323': 'video (audiovisual) communication on local area networks',
+      'h324': ('video and audio communications over low bitrate connections '
+               'such as POTS modem connections'),
+      'hdl': 'CNRI handle system',
+      'hnews': 'an HTTP-tunneling variant of the NNTP news protocol',
+      'http': 'Hypertext Transfer Protocol',
+      'https': 'HTTP over SSL',
+      'hydra': 'SubEthaEdit URI.  See http://www.codingmonkeys.de/subethaedit.',
+      'iioploc': 'Internet Inter-ORB Protocol Location?',
+      'ilu': 'Inter-Language Unification',
+      'im': 'Instant Messaging',
+      'imap': 'Internet Message Access Protocol',
+      'ior': 'CORBA interoperable object reference',
+      'ipp': 'Internet Printing Protocol',
+      'irc': 'Internet Relay Chat',
+      'iseek' : 'See www.ambrosiasw.com;  a little util for OS X.',
+      'jar': 'Java archive',
+      'javascript': ('JavaScript code; evaluates the expression after the '
+                     'colon'),
+      'jdbc': 'JDBC connection URI.',
+      'ldap': 'Lightweight Directory Access Protocol',
+      'lifn': '',
+      'livescript': '',
+      'lrq': '',
+      'mailbox': 'Mail folder access',
+      'mailserver': 'Access to data available from mail servers',
+      'mailto': 'Electronic mail address',
+      'md5': '',
+      'mid': 'message identifier',
+      'mocha': '',
+      'modem': ('a connection to a terminal that can handle incoming data '
+                'calls; RFC 2806'),
+      'mupdate': 'Mailbox Update (MUPDATE) Protocol',
+      'news': 'USENET news',
+      'nfs': 'Network File System protocol',
+      'nntp': 'USENET news using NNTP access',
+      'opaquelocktoken': '',
+      'phone': '',
+      'pop': 'Post Office Protocol',
+      'pop3': 'Post Office Protocol v3',
+      'pres': 'Presence',
+      'printer': '',
+      'prospero': 'Prospero Directory Service',
+      'rdar' : 'URLs found in Darwin source (http://www.opensource.apple.com/darwinsource/).',
+      'res': '',
+      'rtsp': 'real time streaming protocol',
+      'rvp': '',
+      'rwhois': '',
+      'rx': 'Remote Execution',
+      'sdp': '',
+      'service': 'service location',
+      'shttp': 'secure hypertext transfer protocol',
+      'sip': 'Session Initiation Protocol',
+      'sips': 'secure session intitiaion protocol',
+      'smb': 'SAMBA filesystems.',
+      'snews': 'For NNTP postings via SSL',
+      'soap.beep': '',
+      'soap.beeps': '',
+      'ssh': 'Reference to interactive sessions via ssh.',
+      't120': 'real time data conferencing (audiographics)',
+      'tcp': '',
+      'tel': ('a connection to a terminal that handles normal voice '
+              'telephone calls, a voice mailbox or another voice messaging '
+              'system or a service that can be operated using DTMF tones; '
+              'RFC 2806.'),
+      'telephone': 'telephone',
+      'telnet': 'Reference to interactive sessions',
+      'tftp': 'Trivial File Transfer Protocol',
+      'tip': 'Transaction Internet Protocol',
+      'tn3270': 'Interactive 3270 emulation sessions',
+      'tv': '',
+      'urn': 'Uniform Resource Name',
+      'uuid': '',
+      'vemmi': 'versatile multimedia interface',
+      'videotex': '',
+      'view-source': 'displays HTML code that was generated with JavaScript',
+      'wais': 'Wide Area Information Servers',
+      'whodp': '',
+      'whois++': 'Distributed directory service.',
+      'x-man-page': 'Opens man page in Terminal.app on OS X (see macosxhints.com)',
+      'xmlrpc.beep': '',
+      'xmlrpc.beeps': '',
+      'z39.50r': 'Z39.50 Retrieval',
+      'z39.50s': 'Z39.50 Session',}

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/utils.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/utils.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/utils.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,579 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:02 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Miscellaneous utilities for the documentation utilities.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import sys
+import os
+import os.path
+from types import StringType, UnicodeType
+from docutils import ApplicationError, DataError
+from docutils import frontend, nodes
+
+
+class SystemMessage(ApplicationError):
+
+    def __init__(self, system_message, level):
+        Exception.__init__(self, system_message.astext())
+        self.level = level
+
+
+class SystemMessagePropagation(ApplicationError): pass
+
+
+class Reporter:
+
+    """
+    Info/warning/error reporter and ``system_message`` element generator.
+
+    Five levels of system messages are defined, along with corresponding
+    methods: `debug()`, `info()`, `warning()`, `error()`, and `severe()`.
+
+    There is typically one Reporter object per process.  A Reporter object is
+    instantiated with thresholds for reporting (generating warnings) and
+    halting processing (raising exceptions), a switch to turn debug output on
+    or off, and an I/O stream for warnings.  These are stored in the default
+    reporting category, '' (zero-length string).
+
+    Multiple reporting categories [#]_ may be set, each with its own reporting
+    and halting thresholds, debugging switch, and warning stream
+    (collectively a `ConditionSet`).  Categories are hierarchical dotted-name
+    strings that look like attribute references: 'spam', 'spam.eggs',
+    'neeeow.wum.ping'.  The 'spam' category is the ancestor of
+    'spam.bacon.eggs'.  Unset categories inherit stored conditions from their
+    closest ancestor category that has been set.
+
+    When a system message is generated, the stored conditions from its
+    category (or ancestor if unset) are retrieved.  The system message level
+    is compared to the thresholds stored in the category, and a warning or
+    error is generated as appropriate.  Debug messages are produced iff the
+    stored debug switch is on.  Message output is sent to the stored warning
+    stream if not set to ''.
+
+    The default category is '' (empty string).  By convention, Writers should
+    retrieve reporting conditions from the 'writer' category (which, unless
+    explicitly set, defaults to the conditions of the default category).
+
+    The Reporter class also employs a modified form of the "Observer" pattern
+    [GoF95]_ to track system messages generated.  The `attach_observer` method
+    should be called before parsing, with a bound method or function which
+    accepts system messages.  The observer can be removed with
+    `detach_observer`, and another added in its place.
+
+    .. [#] The concept of "categories" was inspired by the log4j project:
+       http://jakarta.apache.org/log4j/.
+
+    .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
+       Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
+       1995.
+    """
+
+    levels = 'DEBUG INFO WARNING ERROR SEVERE'.split()
+    """List of names for system message levels, indexed by level."""
+
+    def __init__(self, source, report_level, halt_level, stream=None,
+                 debug=0, encoding='ascii', error_handler='replace'):
+        """
+        Initialize the `ConditionSet` forthe `Reporter`'s default category.
+
+        :Parameters:
+
+            - `source`: The path to or description of the source data.
+            - `report_level`: The level at or above which warning output will
+              be sent to `stream`.
+            - `halt_level`: The level at or above which `SystemMessage`
+              exceptions will be raised, halting execution.
+            - `debug`: Show debug (level=0) system messages?
+            - `stream`: Where warning output is sent.  Can be file-like (has a
+              ``.write`` method), a string (file name, opened for writing),
+              '' (empty string, for discarding all stream messages) or
+              `None` (implies `sys.stderr`; default).
+            - `encoding`: The encoding for stderr output.
+            - `error_handler`: The error handler for stderr output encoding.
+        """
+        self.source = source
+        """The path to or description of the source data."""
+
+        if stream is None:
+            stream = sys.stderr
+        elif type(stream) in (StringType, UnicodeType):
+            # Leave stream untouched if it's ''.
+            if stream != '':
+                if type(stream) == StringType:
+                    stream = open(stream, 'w')
+                elif type(stream) == UnicodeType:
+                    stream = open(stream.encode(), 'w')
+
+        self.encoding = encoding
+        """The character encoding for the stderr output."""
+
+        self.error_handler = error_handler
+        """The character encoding error handler."""
+
+        self.categories = {'': ConditionSet(debug, report_level, halt_level,
+                                            stream)}
+        """Mapping of category names to conditions. Default category is ''."""
+
+        self.observers = []
+        """List of bound methods or functions to call with each system_message
+        created."""
+
+        self.max_level = -1
+        """The highest level system message generated so far."""
+
+    def set_conditions(self, category, report_level, halt_level,
+                       stream=None, debug=0):
+        if stream is None:
+            stream = sys.stderr
+        self.categories[category] = ConditionSet(debug, report_level,
+                                                 halt_level, stream)
+
+    def unset_conditions(self, category):
+        if category and self.categories.has_key(category):
+            del self.categories[category]
+
+    __delitem__ = unset_conditions
+
+    def get_conditions(self, category):
+        while not self.categories.has_key(category):
+            category = category[:category.rfind('.') + 1][:-1]
+        return self.categories[category]
+
+    __getitem__ = get_conditions
+
+    def attach_observer(self, observer):
+        """
+        The `observer` parameter is a function or bound method which takes one
+        argument, a `nodes.system_message` instance.
+        """
+        self.observers.append(observer)
+
+    def detach_observer(self, observer):
+        self.observers.remove(observer)
+
+    def notify_observers(self, message):
+        for observer in self.observers:
+            observer(message)
+
+    def system_message(self, level, message, *children, **kwargs):
+        """
+        Return a system_message object.
+
+        Raise an exception or generate a warning if appropriate.
+        """
+        attributes = kwargs.copy()
+        category = kwargs.get('category', '')
+        if kwargs.has_key('category'):
+            del attributes['category']
+        if kwargs.has_key('base_node'):
+            source, line = get_source_line(kwargs['base_node'])
+            del attributes['base_node']
+            if source is not None:
+                attributes.setdefault('source', source)
+            if line is not None:
+                attributes.setdefault('line', line)
+        attributes.setdefault('source', self.source)
+        msg = nodes.system_message(message, level=level,
+                                   type=self.levels[level],
+                                   *children, **attributes)
+        debug, report_level, halt_level, stream = self[category].astuple()
+        if (level >= report_level or debug and level == 0) and stream:
+            msgtext = msg.astext().encode(self.encoding, self.error_handler)
+            if category:
+                print >>stream, msgtext, '[%s]' % category
+            else:
+                print >>stream, msgtext
+        if level >= halt_level:
+            raise SystemMessage(msg, level)
+        if level > 0 or debug:
+            self.notify_observers(msg)
+        self.max_level = max(level, self.max_level)
+        return msg
+
+    def debug(self, *args, **kwargs):
+        """
+        Level-0, "DEBUG": an internal reporting issue. Typically, there is no
+        effect on the processing. Level-0 system messages are handled
+        separately from the others.
+        """
+        return self.system_message(0, *args, **kwargs)
+
+    def info(self, *args, **kwargs):
+        """
+        Level-1, "INFO": a minor issue that can be ignored. Typically there is
+        no effect on processing, and level-1 system messages are not reported.
+        """
+        return self.system_message(1, *args, **kwargs)
+
+    def warning(self, *args, **kwargs):
+        """
+        Level-2, "WARNING": an issue that should be addressed. If ignored,
+        there may be unpredictable problems with the output.
+        """
+        return self.system_message(2, *args, **kwargs)
+
+    def error(self, *args, **kwargs):
+        """
+        Level-3, "ERROR": an error that should be addressed. If ignored, the
+        output will contain errors.
+        """
+        return self.system_message(3, *args, **kwargs)
+
+    def severe(self, *args, **kwargs):
+        """
+        Level-4, "SEVERE": a severe error that must be addressed. If ignored,
+        the output will contain severe errors. Typically level-4 system
+        messages are turned into exceptions which halt processing.
+        """
+        return self.system_message(4, *args, **kwargs)
+
+
+class ConditionSet:
+
+    """
+    A set of two thresholds (`report_level` & `halt_level`), a switch
+    (`debug`), and an I/O stream (`stream`), corresponding to one `Reporter`
+    category.
+    """
+
+    def __init__(self, debug, report_level, halt_level, stream):
+        self.debug = debug
+        self.report_level = report_level
+        self.halt_level = halt_level
+        self.stream = stream
+
+    def astuple(self):
+        return (self.debug, self.report_level, self.halt_level,
+                self.stream)
+
+
+class ExtensionOptionError(DataError): pass
+class BadOptionError(ExtensionOptionError): pass
+class BadOptionDataError(ExtensionOptionError): pass
+class DuplicateOptionError(ExtensionOptionError): pass
+
+
+def extract_extension_options(field_list, options_spec):
+    """
+    Return a dictionary mapping extension option names to converted values.
+
+    :Parameters:
+        - `field_list`: A flat field list without field arguments, where each
+          field body consists of a single paragraph only.
+        - `options_spec`: Dictionary mapping known option names to a
+          conversion function such as `int` or `float`.
+
+    :Exceptions:
+        - `KeyError` for unknown option names.
+        - `ValueError` for invalid option values (raised by the conversion
+           function).
+        - `TypeError` for invalid option value types (raised by conversion
+           function).
+        - `DuplicateOptionError` for duplicate options.
+        - `BadOptionError` for invalid fields.
+        - `BadOptionDataError` for invalid option data (missing name,
+          missing data, bad quotes, etc.).
+    """
+    option_list = extract_options(field_list)
+    option_dict = assemble_option_dict(option_list, options_spec)
+    return option_dict
+
+def extract_options(field_list):
+    """
+    Return a list of option (name, value) pairs from field names & bodies.
+
+    :Parameter:
+        `field_list`: A flat field list, where each field name is a single
+        word and each field body consists of a single paragraph only.
+
+    :Exceptions:
+        - `BadOptionError` for invalid fields.
+        - `BadOptionDataError` for invalid option data (missing name,
+          missing data, bad quotes, etc.).
+    """
+    option_list = []
+    for field in field_list:
+        if len(field[0].astext().split()) != 1:
+            raise BadOptionError(
+                'extension option field name may not contain multiple words')
+        name = str(field[0].astext().lower())
+        body = field[1]
+        if len(body) == 0:
+            data = None
+        elif len(body) > 1 or not isinstance(body[0], nodes.paragraph) \
+              or len(body[0]) != 1 or not isinstance(body[0][0], nodes.Text):
+            raise BadOptionDataError(
+                  'extension option field body may contain\n'
+                  'a single paragraph only (option "%s")' % name)
+        else:
+            data = body[0][0].astext()
+        option_list.append((name, data))
+    return option_list
+
+def assemble_option_dict(option_list, options_spec):
+    """
+    Return a mapping of option names to values.
+
+    :Parameters:
+        - `option_list`: A list of (name, value) pairs (the output of
+          `extract_options()`).
+        - `options_spec`: Dictionary mapping known option names to a
+          conversion function such as `int` or `float`.
+
+    :Exceptions:
+        - `KeyError` for unknown option names.
+        - `DuplicateOptionError` for duplicate options.
+        - `ValueError` for invalid option values (raised by conversion
+           function).
+        - `TypeError` for invalid option value types (raised by conversion
+           function).
+    """
+    options = {}
+    for name, value in option_list:
+        convertor = options_spec[name]  # raises KeyError if unknown
+        if convertor is None:
+            raise KeyError(name)        # or if explicitly disabled
+        if options.has_key(name):
+            raise DuplicateOptionError('duplicate option "%s"' % name)
+        try:
+            options[name] = convertor(value)
+        except (ValueError, TypeError), detail:
+            raise detail.__class__('(option: "%s"; value: %r)\n%s'
+                                   % (name, value, detail))
+    return options
+
+
+class NameValueError(DataError): pass
+
+
+def extract_name_value(line):
+    """
+    Return a list of (name, value) from a line of the form "name=value ...".
+
+    :Exception:
+        `NameValueError` for invalid input (missing name, missing data, bad
+        quotes, etc.).
+    """
+    attlist = []
+    while line:
+        equals = line.find('=')
+        if equals == -1:
+            raise NameValueError('missing "="')
+        attname = line[:equals].strip()
+        if equals == 0 or not attname:
+            raise NameValueError(
+                  'missing attribute name before "="')
+        line = line[equals+1:].lstrip()
+        if not line:
+            raise NameValueError(
+                  'missing value after "%s="' % attname)
+        if line[0] in '\'"':
+            endquote = line.find(line[0], 1)
+            if endquote == -1:
+                raise NameValueError(
+                      'attribute "%s" missing end quote (%s)'
+                      % (attname, line[0]))
+            if len(line) > endquote + 1 and line[endquote + 1].strip():
+                raise NameValueError(
+                      'attribute "%s" end quote (%s) not followed by '
+                      'whitespace' % (attname, line[0]))
+            data = line[1:endquote]
+            line = line[endquote+1:].lstrip()
+        else:
+            space = line.find(' ')
+            if space == -1:
+                data = line
+                line = ''
+            else:
+                data = line[:space]
+                line = line[space+1:].lstrip()
+        attlist.append((attname.lower(), data))
+    return attlist
+
+def new_document(source, settings=None):
+    """
+    Return a new empty document object.
+
+    :Parameters:
+        `source` : string
+            The path to or description of the source text of the document.
+        `settings` : optparse.Values object
+            Runtime settings.  If none provided, a default set will be used.
+    """
+    if settings is None:
+        settings = frontend.OptionParser().get_default_values()
+    reporter = Reporter(source, settings.report_level, settings.halt_level,
+                        stream=settings.warning_stream, debug=settings.debug,
+                        encoding=settings.error_encoding,
+                        error_handler=settings.error_encoding_error_handler)
+    document = nodes.document(settings, reporter, source=source)
+    document.note_source(source, -1)
+    return document
+
+def clean_rcs_keywords(paragraph, keyword_substitutions):
+    if len(paragraph) == 1 and isinstance(paragraph[0], nodes.Text):
+        textnode = paragraph[0]
+        for pattern, substitution in keyword_substitutions:
+            match = pattern.search(textnode.data)
+            if match:
+                textnode.data = pattern.sub(substitution, textnode.data)
+                return
+
+def relative_path(source, target):
+    """
+    Build and return a path to `target`, relative to `source` (both files).
+
+    If there is no common prefix, return the absolute path to `target`.
+    """
+    source_parts = os.path.abspath(source or 'dummy_file').split(os.sep)
+    target_parts = os.path.abspath(target).split(os.sep)
+    # Check first 2 parts because '/dir'.split('/') == ['', 'dir']:
+    if source_parts[:2] != target_parts[:2]:
+        # Nothing in common between paths.
+        # Return absolute path, using '/' for URLs:
+        return '/'.join(target_parts)
+    source_parts.reverse()
+    target_parts.reverse()
+    while (source_parts and target_parts
+           and source_parts[-1] == target_parts[-1]):
+        # Remove path components in common:
+        source_parts.pop()
+        target_parts.pop()
+    target_parts.reverse()
+    parts = ['..'] * (len(source_parts) - 1) + target_parts
+    return '/'.join(parts)
+
+def get_stylesheet_reference(settings, relative_to=None):
+    """
+    Retrieve a stylesheet reference from the settings object.
+    """
+    if settings.stylesheet_path:
+        assert not settings.stylesheet, \
+               'stylesheet and stylesheet_path are mutually exclusive.'
+        if relative_to == None:
+            relative_to = settings._destination
+        return relative_path(relative_to, settings.stylesheet_path)
+    else:
+        return settings.stylesheet
+
+def get_trim_footnote_ref_space(settings):
+    """
+    Return whether or not to trim footnote space.
+
+    If trim_footnote_reference_space is not None, return it.
+
+    If trim_footnote_reference_space is None, return False unless the
+    footnote reference style is 'superscript'.
+    """
+    if settings.trim_footnote_reference_space is None:
+        return hasattr(settings, 'footnote_references') and \
+               settings.footnote_references == 'superscript'
+    else:
+        return settings.trim_footnote_reference_space
+
+def get_source_line(node):
+    """
+    Return the "source" and "line" attributes from the `node` given or from
+    its closest ancestor.
+    """
+    while node:
+        if node.source or node.line:
+            return node.source, node.line
+        node = node.parent
+    return None, None
+
+def escape2null(text):
+    """Return a string with escape-backslashes converted to nulls."""
+    parts = []
+    start = 0
+    while 1:
+        found = text.find('\\', start)
+        if found == -1:
+            parts.append(text[start:])
+            return ''.join(parts)
+        parts.append(text[start:found])
+        parts.append('\x00' + text[found+1:found+2])
+        start = found + 2               # skip character after escape
+
+def unescape(text, restore_backslashes=0):
+    """
+    Return a string with nulls removed or restored to backslashes.
+    Backslash-escaped spaces are also removed.
+    """
+    if restore_backslashes:
+        return text.replace('\x00', '\\')
+    else:
+        for sep in ['\x00 ', '\x00\n', '\x00']:
+            text = ''.join(text.split(sep))
+        return text
+
+
+class DependencyList:
+
+    """
+    List of dependencies, with file recording support.
+
+    Note that the output file is not automatically closed.  You have
+    to explicitly call the close() method.
+    """
+
+    def __init__(self, output_file=None, dependencies=[]):
+        """
+        Initialize the dependency list, automatically setting the
+        output file to `output_file` (see `set_output()`) and adding
+        all supplied dependencies.
+        """
+        self.set_output(output_file)
+        for i in dependencies:
+            self.add(i)
+
+    def set_output(self, output_file):
+        """
+        Set the output file and clear the list of already added
+        dependencies.
+
+        `output_file` must be a string.  The specified file is
+        immediately overwritten.
+
+        If output_file is '-', the output will be written to stdout.
+        If it is None, no file output is done when calling add().
+        """
+        self.list = []
+        if output_file == '-':
+            self.file = sys.stdout
+        elif output_file:
+            self.file = open(output_file, 'w')
+        else:
+            self.file = None
+
+    def add(self, filename):
+        """
+        If the dependency `filename` has not already been added,
+        append it to self.list and print it to self.file if self.file
+        is not None.
+        """
+        if not filename in self.list:
+            self.list.append(filename)
+            if self.file is not None:
+                print >>self.file, filename
+
+    def close(self):
+        """
+        Close the output file.
+        """
+        self.file.close()
+        self.file = None
+
+    def __repr__(self):
+        if self.file:
+            output_file = self.file.name
+        else:
+            output_file = None
+        return '%s(%r, %s)' % (self.__class__.__name__, output_file, self.list)

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/__init__.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/__init__.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/__init__.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,109 @@
+# Authors: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+This package contains Docutils Writer modules.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+import docutils
+from docutils import languages, Component
+from docutils.transforms import universal
+
+
+class Writer(Component):
+
+    """
+    Abstract base class for docutils Writers.
+
+    Each writer module or package must export a subclass also called 'Writer'.
+    Each writer must support all standard node types listed in
+    `docutils.nodes.node_class_names`.
+
+    The `write()` method is the main entry point.
+    """
+
+    component_type = 'writer'
+    config_section = 'writers'
+
+    document = None
+    """The document to write (Docutils doctree); set by `write`."""
+
+    output = None
+    """Final translated form of `document` (Unicode string);
+    set by `translate`."""
+
+    language = None
+    """Language module for the document; set by `write`."""
+
+    destination = None
+    """`docutils.io` Output object; where to write the document.
+    Set by `write`."""
+
+    def __init__(self):
+
+        # Currently only used by HTML writer for output fragments:
+        self.parts = {}
+        """Mapping of document part names to fragments of `self.output`.
+        Values are Unicode strings; encoding is up to the client.  The 'whole'
+        key should contain the entire document output.
+        """
+
+    def write(self, document, destination):
+        """
+        Process a document into its final form.
+
+        Translate `document` (a Docutils document tree) into the Writer's
+        native format, and write it out to its `destination` (a
+        `docutils.io.Output` subclass object).
+
+        Normally not overridden or extended in subclasses.
+        """
+        self.document = document
+        self.language = languages.get_language(
+            document.settings.language_code)
+        self.destination = destination
+        self.translate()
+        output = self.destination.write(self.output)
+        return output
+
+    def translate(self):
+        """
+        Do final translation of `self.document` into `self.output` (Unicode
+        string).  Called from `write`.  Override in subclasses.
+
+        Usually done with a `docutils.nodes.NodeVisitor` subclass, in
+        combination with a call to `docutils.nodes.Node.walk()` or
+        `docutils.nodes.Node.walkabout()`.  The ``NodeVisitor`` subclass must
+        support all standard elements (listed in
+        `docutils.nodes.node_class_names`) and possibly non-standard elements
+        used by the current Reader as well.
+        """
+        raise NotImplementedError('subclass must override this method')
+
+    def assemble_parts(self):
+        """Assemble the `self.parts` dictionary.  Extend in subclasses."""
+        self.parts['whole'] = self.output
+
+
+_writer_aliases = {
+      'html': 'html4css1',
+      'latex': 'latex2e',
+      'pprint': 'pseudoxml',
+      'pformat': 'pseudoxml',
+      'pdf': 'rlpdf',
+      'xml': 'docutils_xml',}
+
+def get_writer_class(writer_name):
+    """Return the Writer class from the `writer_name` module."""
+    writer_name = writer_name.lower()
+    if _writer_aliases.has_key(writer_name):
+        writer_name = _writer_aliases[writer_name]
+    module = __import__(writer_name, globals(), locals())
+    return module.Writer

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/docutils_xml.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/docutils_xml.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/docutils_xml.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,73 @@
+# Authors: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple internal document tree Writer, writes Docutils XML.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import docutils
+from docutils import frontend, writers
+
+
+class Writer(writers.Writer):
+
+    supported = ('xml',)
+    """Formats this writer supports."""
+
+    settings_spec = (
+        '"Docutils XML" Writer Options',
+        'Warning: the --newlines and --indents options may adversely affect '
+        'whitespace; use them only for reading convenience.',
+        (('Generate XML with newlines before and after tags.',
+          ['--newlines'],
+          {'action': 'store_true', 'validator': frontend.validate_boolean}),
+         ('Generate XML with indents and newlines.',
+          ['--indents'],
+          {'action': 'store_true', 'validator': frontend.validate_boolean}),
+         ('Omit the XML declaration.  Use with caution.',
+          ['--no-xml-declaration'],
+          {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false',
+           'validator': frontend.validate_boolean}),
+         ('Omit the DOCTYPE declaration.',
+          ['--no-doctype'],
+          {'dest': 'doctype_declaration', 'default': 1,
+           'action': 'store_false', 'validator': frontend.validate_boolean}),))
+
+    config_section = 'docutils_xml writer'
+    config_section_dependencies = ('writers',)
+
+    output = None
+    """Final translated form of `document`."""
+
+    xml_declaration = '<?xml version="1.0" encoding="%s"?>\n'
+    #xml_stylesheet = '<?xml-stylesheet type="text/xsl" href="%s"?>\n'
+    doctype = (
+        '<!DOCTYPE document PUBLIC'
+        ' "+//IDN docutils.sourceforge.net//DTD Docutils Generic//EN//XML"'
+        ' "http://docutils.sourceforge.net/docs/ref/docutils.dtd">\n')
+    generator = '<!-- Generated by Docutils %s -->\n'
+
+    def translate(self):
+        settings = self.document.settings
+        indent = newline = ''
+        if settings.newlines:
+            newline = '\n'
+        if settings.indents:
+            newline = '\n'
+            indent = '    '
+        output_prefix = []
+        if settings.xml_declaration:
+            output_prefix.append(
+                self.xml_declaration % settings.output_encoding)
+        if settings.doctype_declaration:
+            output_prefix.append(self.doctype)
+        output_prefix.append(self.generator % docutils.__version__)
+        docnode = self.document.asdom().childNodes[0]
+        self.output = (''.join(output_prefix)
+                       + docnode.toprettyxml(indent, newline))

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/html4css1.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/html4css1.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/html4css1.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,1389 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.7 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple HyperText Markup Language document tree Writer.
+
+The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
+HTML version 1.0 Transitional DTD (*almost* strict).  The output contains a
+minimum of formatting information.  A cascading style sheet ("default.css" by
+default) is required for proper viewing with a modern graphical browser.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import sys
+import os
+import os.path
+import time
+import re
+from types import ListType
+try:
+    import Image                        # check for the Python Imaging Library
+except ImportError:
+    Image = None
+import docutils
+from docutils import frontend, nodes, utils, writers, languages
+
+
+class Writer(writers.Writer):
+
+    supported = ('html', 'html4css1', 'xhtml')
+    """Formats this writer supports."""
+
+    settings_spec = (
+        'HTML-Specific Options',
+        None,
+        (('Specify a stylesheet URL, used verbatim.  Default is '
+          '"default.css".  Overrides --stylesheet-path.',
+          ['--stylesheet'],
+          {'default': 'default.css', 'metavar': '<URL>',
+           'overrides': 'stylesheet_path'}),
+         ('Specify a stylesheet file, relative to the current working '
+          'directory.  The path is adjusted relative to the output HTML '
+          'file.  Overrides --stylesheet.',
+          ['--stylesheet-path'],
+          {'metavar': '<file>', 'overrides': 'stylesheet'}),
+         ('Link to the stylesheet in the output HTML file.  This is the '
+          'default.',
+          ['--link-stylesheet'],
+          {'dest': 'embed_stylesheet', 'action': 'store_false',
+           'validator': frontend.validate_boolean}),
+         ('Embed the stylesheet in the output HTML file.  The stylesheet '
+          'file must be accessible during processing (--stylesheet-path is '
+          'recommended).  Default: link the stylesheet, do not embed it.',
+          ['--embed-stylesheet'],
+          {'action': 'store_true', 'validator': frontend.validate_boolean}),
+         ('Specify the initial header level.  Default is 1 for "<h1>".  '
+          'Does not affect document title & subtitle (see --no-doc-title).',
+          ['--initial-header-level'],
+          {'choices': '1 2 3 4 5 6'.split(), 'default': '1',
+           'metavar': '<level>'}),
+         ('Format for footnote references: one of "superscript" or '
+          '"brackets".  Default is "brackets".',
+          ['--footnote-references'],
+          {'choices': ['superscript', 'brackets'], 'default': 'brackets',
+           'metavar': '<format>',
+           'overrides': 'trim_footnote_reference_space'}),
+         ('Format for block quote attributions: one of "dash" (em-dash '
+          'prefix), "parentheses"/"parens", or "none".  Default is "dash".',
+          ['--attribution'],
+          {'choices': ['dash', 'parentheses', 'parens', 'none'],
+           'default': 'dash', 'metavar': '<format>'}),
+         ('Remove extra vertical whitespace between items of bullet lists '
+          'and enumerated lists, when list items are "simple" (i.e., all '
+          'items each contain one paragraph and/or one "simple" sublist '
+          'only).  Default: enabled.',
+          ['--compact-lists'],
+          {'default': 1, 'action': 'store_true',
+           'validator': frontend.validate_boolean}),
+         ('Disable compact simple bullet and enumerated lists.',
+          ['--no-compact-lists'],
+          {'dest': 'compact_lists', 'action': 'store_false'}),
+         ('Omit the XML declaration.  Use with caution.',
+          ['--no-xml-declaration'],
+          {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false',
+           'validator': frontend.validate_boolean}),))
+
+    relative_path_settings = ('stylesheet_path',)
+
+    config_section = 'html4css1 writer'
+    config_section_dependencies = ('writers',)
+
+    def __init__(self):
+        writers.Writer.__init__(self)
+        self.translator_class = HTMLTranslator
+
+    def translate(self):
+        visitor = self.translator_class(self.document)
+        self.document.walkabout(visitor)
+        self.output = visitor.astext()
+        self.visitor = visitor
+        for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',
+                     'body_pre_docinfo', 'docinfo', 'body', 'fragment',
+                     'body_suffix'):
+            setattr(self, attr, getattr(visitor, attr))
+
+    def assemble_parts(self):
+        writers.Writer.assemble_parts(self)
+        for part in ('title', 'subtitle', 'docinfo', 'body', 'header',
+                     'footer', 'meta', 'stylesheet', 'fragment'):
+            self.parts[part] = ''.join(getattr(self.visitor, part))
+
+
+class HTMLTranslator(nodes.NodeVisitor):
+
+    """
+    This HTML writer has been optimized to produce visually compact
+    lists (less vertical whitespace).  HTML's mixed content models
+    allow list items to contain "<li><p>body elements</p></li>" or
+    "<li>just text</li>" or even "<li>text<p>and body
+    elements</p>combined</li>", each with different effects.  It would
+    be best to stick with strict body elements in list items, but they
+    affect vertical spacing in browsers (although they really
+    shouldn't).
+
+    Here is an outline of the optimization:
+
+    - Check for and omit <p> tags in "simple" lists: list items
+      contain either a single paragraph, a nested simple list, or a
+      paragraph followed by a nested simple list.  This means that
+      this list can be compact:
+
+          - Item 1.
+          - Item 2.
+
+      But this list cannot be compact:
+
+          - Item 1.
+
+            This second paragraph forces space between list items.
+
+          - Item 2.
+
+    - In non-list contexts, omit <p> tags on a paragraph if that
+      paragraph is the only child of its parent (footnotes & citations
+      are allowed a label first).
+
+    - Regardless of the above, in definitions, table cells, field bodies,
+      option descriptions, and list items, mark the first child with
+      'class="first"' and the last child with 'class="last"'.  The stylesheet
+      sets the margins (top & bottom respectively) to 0 for these elements.
+
+    The ``no_compact_lists`` setting (``--no-compact-lists`` command-line
+    option) disables list whitespace optimization.
+    """
+
+    xml_declaration = '<?xml version="1.0" encoding="%s" ?>\n'
+    doctype = ('<!DOCTYPE html'
+               ' PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
+               ' "http://www.w3.org/TR/xhtml1/DTD/'
+               'xhtml1-transitional.dtd">\n')
+    html_head = ('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="%s" '
+                 'lang="%s">\n<head>\n')
+    content_type = ('<meta http-equiv="Content-Type" content="text/html; '
+                    'charset=%s" />\n')
+    generator = ('<meta name="generator" content="Docutils %s: '
+                 'http://docutils.sourceforge.net/" />\n')
+    stylesheet_link = '<link rel="stylesheet" href="%s" type="text/css" />\n'
+    embedded_stylesheet = '<style type="text/css">\n\n%s\n</style>\n'
+    named_tags = {'a': 1, 'applet': 1, 'form': 1, 'frame': 1, 'iframe': 1,
+                  'img': 1, 'map': 1}
+    words_and_spaces = re.compile(r'\S+| +|\n')
+
+    def __init__(self, document):
+        nodes.NodeVisitor.__init__(self, document)
+        self.settings = settings = document.settings
+        lcode = settings.language_code
+        self.language = languages.get_language(lcode)
+        self.meta = [self.content_type % settings.output_encoding,
+                     self.generator % docutils.__version__]
+        self.head_prefix = [
+              self.doctype,
+              self.html_head % (lcode, lcode)]
+        self.head_prefix.extend(self.meta)
+        if settings.xml_declaration:
+            self.head_prefix.insert(0, self.xml_declaration
+                                    % settings.output_encoding)
+        self.head = []
+        if settings.embed_stylesheet:
+            stylesheet = utils.get_stylesheet_reference(settings,
+                os.path.join(os.getcwd(), 'dummy'))
+            settings.record_dependencies.add(stylesheet)
+            stylesheet_text = open(stylesheet).read()
+            self.stylesheet = [self.embedded_stylesheet % stylesheet_text]
+        else:
+            stylesheet = utils.get_stylesheet_reference(settings)
+            if stylesheet:
+                self.stylesheet = [self.stylesheet_link % stylesheet]
+            else:
+                self.stylesheet = []
+        self.body_prefix = ['</head>\n<body>\n']
+        # document title, subtitle display
+        self.body_pre_docinfo = []
+        # author, date, etc.
+        self.docinfo = []
+        self.body = []
+        self.fragment = []
+        self.body_suffix = ['</body>\n</html>\n']
+        self.section_level = 0
+        self.initial_header_level = int(settings.initial_header_level)
+        # A heterogenous stack used in conjunction with the tree traversal.
+        # Make sure that the pops correspond to the pushes:
+        self.context = []
+        self.topic_class = ''
+        self.colspecs = []
+        self.compact_p = 1
+        self.compact_simple = None
+        self.in_docinfo = None
+        self.in_sidebar = None
+        self.title = []
+        self.subtitle = []
+        self.header = []
+        self.footer = []
+        self.in_document_title = 0
+
+    def astext(self):
+        return ''.join(self.head_prefix + self.head
+                       + self.stylesheet + self.body_prefix
+                       + self.body_pre_docinfo + self.docinfo
+                       + self.body + self.body_suffix)
+
+    def encode(self, text):
+        """Encode special characters in `text` & return."""
+        # @@@ A codec to do these and all other HTML entities would be nice.
+        text = text.replace("&", "&amp;")
+        text = text.replace("<", "&lt;")
+        text = text.replace('"', "&quot;")
+        text = text.replace(">", "&gt;")
+        text = text.replace("@", "&#64;") # may thwart some address harvesters
+        # Replace the non-breaking space character with the HTML entity:
+        text = text.replace(u'\u00a0', "&nbsp;")
+        return text
+
+    def attval(self, text,
+               whitespace=re.compile('[\n\r\t\v\f]')):
+        """Cleanse, HTML encode, and return attribute value text."""
+        return self.encode(whitespace.sub(' ', text))
+
+    def starttag(self, node, tagname, suffix='\n', infix='', **attributes):
+        """
+        Construct and return a start tag given a node (id & class attributes
+        are extracted), tag name, and optional attributes.
+        """
+        tagname = tagname.lower()
+        atts = {}
+        for (name, value) in attributes.items():
+            atts[name.lower()] = value
+        for att in ('class',):          # append to node attribute
+            if node.has_key(att) or atts.has_key(att):
+                atts[att] = \
+                      (node.get(att, '') + ' ' + atts.get(att, '')).strip()
+        for att in ('id',):             # node attribute overrides
+            if node.has_key(att):
+                atts[att] = node[att]
+        if atts.has_key('id') and self.named_tags.has_key(tagname):
+            atts['name'] = atts['id']   # for compatibility with old browsers
+        attlist = atts.items()
+        attlist.sort()
+        parts = [tagname]
+        for name, value in attlist:
+            # value=None was used for boolean attributes without
+            # value, but this isn't supported by XHTML.
+            assert value is not None
+            if isinstance(value, ListType):
+                values = [unicode(v) for v in value]
+                parts.append('%s="%s"' % (name.lower(),
+                                          self.attval(' '.join(values))))
+            else:
+                try:
+                    uval = unicode(value)
+                except TypeError:       # for Python 2.1 compatibility:
+                    uval = unicode(str(value))
+                parts.append('%s="%s"' % (name.lower(), self.attval(uval)))
+        return '<%s%s>%s' % (' '.join(parts), infix, suffix)
+
+    def emptytag(self, node, tagname, suffix='\n', **attributes):
+        """Construct and return an XML-compatible empty tag."""
+        return self.starttag(node, tagname, suffix, infix=' /', **attributes)
+
+    def set_first_last(self, node):
+        if len(node):
+            node[0].set_class('first')
+            node[-1].set_class('last')
+
+    def visit_Text(self, node):
+        self.body.append(self.encode(node.astext()))
+
+    def depart_Text(self, node):
+        pass
+
+    def visit_abbreviation(self, node):
+        # @@@ implementation incomplete ("title" attribute)
+        self.body.append(self.starttag(node, 'abbr', ''))
+
+    def depart_abbreviation(self, node):
+        self.body.append('</abbr>')
+
+    def visit_acronym(self, node):
+        # @@@ implementation incomplete ("title" attribute)
+        self.body.append(self.starttag(node, 'acronym', ''))
+
+    def depart_acronym(self, node):
+        self.body.append('</acronym>')
+
+    def visit_address(self, node):
+        self.visit_docinfo_item(node, 'address', meta=None)
+        self.body.append(self.starttag(node, 'pre', CLASS='address'))
+
+    def depart_address(self, node):
+        self.body.append('\n</pre>\n')
+        self.depart_docinfo_item()
+
+    def visit_admonition(self, node, name=''):
+        self.body.append(self.starttag(node, 'div',
+                                        CLASS=(name or 'admonition')))
+        if name:
+            node.insert(0, nodes.title(name, self.language.labels[name]))
+        self.set_first_last(node)
+
+    def depart_admonition(self, node=None):
+        self.body.append('</div>\n')
+
+    def visit_attention(self, node):
+        self.visit_admonition(node, 'attention')
+
+    def depart_attention(self, node):
+        self.depart_admonition()
+
+    attribution_formats = {'dash': ('&mdash;', ''),
+                           'parentheses': ('(', ')'),
+                           'parens': ('(', ')'),
+                           'none': ('', '')}
+
+    def visit_attribution(self, node):
+        prefix, suffix = self.attribution_formats[self.settings.attribution]
+        self.context.append(suffix)
+        self.body.append(
+            self.starttag(node, 'p', prefix, CLASS='attribution'))
+
+    def depart_attribution(self, node):
+        self.body.append(self.context.pop() + '</p>\n')
+
+    def visit_author(self, node):
+        self.visit_docinfo_item(node, 'author')
+
+    def depart_author(self, node):
+        self.depart_docinfo_item()
+
+    def visit_authors(self, node):
+        pass
+
+    def depart_authors(self, node):
+        pass
+
+    def visit_block_quote(self, node):
+        self.body.append(self.starttag(node, 'blockquote'))
+
+    def depart_block_quote(self, node):
+        self.body.append('</blockquote>\n')
+
+    def check_simple_list(self, node):
+        """Check for a simple list that can be rendered compactly."""
+        visitor = SimpleListChecker(self.document)
+        try:
+            node.walk(visitor)
+        except nodes.NodeFound:
+            return None
+        else:
+            return 1
+
+    def visit_bullet_list(self, node):
+        atts = {}
+        old_compact_simple = self.compact_simple
+        self.context.append((self.compact_simple, self.compact_p))
+        self.compact_p = None
+        self.compact_simple = (self.settings.compact_lists and
+                               (self.compact_simple
+                                or self.topic_class == 'contents'
+                                or self.check_simple_list(node)))
+        if self.compact_simple and not old_compact_simple:
+            atts['class'] = 'simple'
+        self.body.append(self.starttag(node, 'ul', **atts))
+
+    def depart_bullet_list(self, node):
+        self.compact_simple, self.compact_p = self.context.pop()
+        self.body.append('</ul>\n')
+
+    def visit_caption(self, node):
+        self.body.append(self.starttag(node, 'p', '', CLASS='caption'))
+
+    def depart_caption(self, node):
+        self.body.append('</p>\n')
+
+    def visit_caution(self, node):
+        self.visit_admonition(node, 'caution')
+
+    def depart_caution(self, node):
+        self.depart_admonition()
+
+    def visit_citation(self, node):
+        self.body.append(self.starttag(node, 'table',
+                                       CLASS='docutils citation',
+                                       frame="void", rules="none"))
+        self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
+                         '<tbody valign="top">\n'
+                         '<tr>')
+        self.footnote_backrefs(node)
+
+    def depart_citation(self, node):
+        self.body.append('</td></tr>\n'
+                         '</tbody>\n</table>\n')
+
+    def visit_citation_reference(self, node):
+        href = ''
+        if node.has_key('refid'):
+            href = '#' + node['refid']
+        elif node.has_key('refname'):
+            href = '#' + self.document.nameids[node['refname']]
+        self.body.append(self.starttag(node, 'a', '[',
+                                       CLASS='citation-reference',
+                                       **(href and {'href': href} or {})))
+
+    def depart_citation_reference(self, node):
+        self.body.append(']</a>')
+
+    def visit_classifier(self, node):
+        self.body.append(' <span class="classifier-delimiter">:</span> ')
+        self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))
+
+    def depart_classifier(self, node):
+        self.body.append('</span>')
+
+    def visit_colspec(self, node):
+        self.colspecs.append(node)
+
+    def depart_colspec(self, node):
+        pass
+
+    def write_colspecs(self):
+        width = 0
+        for node in self.colspecs:
+            width += node['colwidth']
+        for node in self.colspecs:
+            colwidth = int(node['colwidth'] * 100.0 / width + 0.5)
+            self.body.append(self.emptytag(node, 'col',
+                                           width='%i%%' % colwidth))
+        self.colspecs = []
+
+    def visit_comment(self, node,
+                      sub=re.compile('-(?=-)').sub):
+        """Escape double-dashes in comment text."""
+        self.body.append('<!-- %s -->\n' % sub('- ', node.astext()))
+        # Content already processed:
+        raise nodes.SkipNode
+
+    def visit_compound(self, node):
+        self.body.append(self.starttag(node, 'div', CLASS='compound'))
+        if len(node) > 1:
+            node[0].set_class('compound-first')
+            node[-1].set_class('compound-last')
+            for child in node[1:-1]:
+                child.set_class('compound-middle')
+
+    def depart_compound(self, node):
+        self.body.append('</div>\n')
+
+    def visit_contact(self, node):
+        self.visit_docinfo_item(node, 'contact', meta=None)
+
+    def depart_contact(self, node):
+        self.depart_docinfo_item()
+
+    def visit_copyright(self, node):
+        self.visit_docinfo_item(node, 'copyright')
+
+    def depart_copyright(self, node):
+        self.depart_docinfo_item()
+
+    def visit_danger(self, node):
+        self.visit_admonition(node, 'danger')
+
+    def depart_danger(self, node):
+        self.depart_admonition()
+
+    def visit_date(self, node):
+        self.visit_docinfo_item(node, 'date')
+
+    def depart_date(self, node):
+        self.depart_docinfo_item()
+
+    def visit_decoration(self, node):
+        pass
+
+    def depart_decoration(self, node):
+        pass
+
+    def visit_definition(self, node):
+        self.body.append('</dt>\n')
+        self.body.append(self.starttag(node, 'dd', ''))
+        self.set_first_last(node)
+
+    def depart_definition(self, node):
+        self.body.append('</dd>\n')
+
+    def visit_definition_list(self, node):
+        self.body.append(self.starttag(node, 'dl', CLASS='docutils'))
+
+    def depart_definition_list(self, node):
+        self.body.append('</dl>\n')
+
+    def visit_definition_list_item(self, node):
+        pass
+
+    def depart_definition_list_item(self, node):
+        pass
+
+    def visit_description(self, node):
+        self.body.append(self.starttag(node, 'td', ''))
+        self.set_first_last(node)
+
+    def depart_description(self, node):
+        self.body.append('</td>')
+
+    def visit_docinfo(self, node):
+        self.context.append(len(self.body))
+        self.body.append(self.starttag(node, 'table',
+                                       CLASS='docinfo',
+                                       frame="void", rules="none"))
+        self.body.append('<col class="docinfo-name" />\n'
+                         '<col class="docinfo-content" />\n'
+                         '<tbody valign="top">\n')
+        self.in_docinfo = 1
+
+    def depart_docinfo(self, node):
+        self.body.append('</tbody>\n</table>\n')
+        self.in_docinfo = None
+        start = self.context.pop()
+        self.docinfo = self.body[start:]
+        self.body = []
+
+    def visit_docinfo_item(self, node, name, meta=1):
+        if meta:
+            meta_tag = '<meta name="%s" content="%s" />\n' \
+                       % (name, self.attval(node.astext()))
+            self.add_meta(meta_tag)
+        self.body.append(self.starttag(node, 'tr', ''))
+        self.body.append('<th class="docinfo-name">%s:</th>\n<td>'
+                         % self.language.labels[name])
+        if len(node):
+            if isinstance(node[0], nodes.Element):
+                node[0].set_class('first')
+            if isinstance(node[-1], nodes.Element):
+                node[-1].set_class('last')
+
+    def depart_docinfo_item(self):
+        self.body.append('</td></tr>\n')
+
+    def visit_doctest_block(self, node):
+        self.body.append(self.starttag(node, 'pre', CLASS='doctest-block'))
+
+    def depart_doctest_block(self, node):
+        self.body.append('\n</pre>\n')
+
+    def visit_document(self, node):
+        # empty or untitled document?
+        if not len(node) or not isinstance(node[0], nodes.title):
+            # for XHTML conformance, modulo IE6 appeasement:
+            self.head.insert(0, '<title></title>\n')
+
+    def depart_document(self, node):
+        self.fragment.extend(self.body)
+        self.body_prefix.append(self.starttag(node, 'div', CLASS='document'))
+        self.body_suffix.insert(0, '</div>\n')
+
+    def visit_emphasis(self, node):
+        self.body.append('<em>')
+
+    def depart_emphasis(self, node):
+        self.body.append('</em>')
+
+    def visit_entry(self, node):
+        if isinstance(node.parent.parent, nodes.thead):
+            tagname = 'th'
+        else:
+            tagname = 'td'
+        atts = {}
+        if node.has_key('morerows'):
+            atts['rowspan'] = node['morerows'] + 1
+        if node.has_key('morecols'):
+            atts['colspan'] = node['morecols'] + 1
+        self.body.append(self.starttag(node, tagname, '', **atts))
+        self.context.append('</%s>\n' % tagname.lower())
+        if len(node) == 0:              # empty cell
+            self.body.append('&nbsp;')
+        self.set_first_last(node)
+
+    def depart_entry(self, node):
+        self.body.append(self.context.pop())
+
+    def visit_enumerated_list(self, node):
+        """
+        The 'start' attribute does not conform to HTML 4.01's strict.dtd, but
+        CSS1 doesn't help. CSS2 isn't widely enough supported yet to be
+        usable.
+        """
+        atts = {}
+        if node.has_key('start'):
+            atts['start'] = node['start']
+        if node.has_key('enumtype'):
+            atts['class'] = node['enumtype']
+        # @@@ To do: prefix, suffix. How? Change prefix/suffix to a
+        # single "format" attribute? Use CSS2?
+        old_compact_simple = self.compact_simple
+        self.context.append((self.compact_simple, self.compact_p))
+        self.compact_p = None
+        self.compact_simple = (self.settings.compact_lists and
+                               (self.compact_simple
+                                or self.topic_class == 'contents'
+                                or self.check_simple_list(node)))
+        if self.compact_simple and not old_compact_simple:
+            atts['class'] = (atts.get('class', '') + ' simple').strip()
+        self.body.append(self.starttag(node, 'ol', **atts))
+
+    def depart_enumerated_list(self, node):
+        self.compact_simple, self.compact_p = self.context.pop()
+        self.body.append('</ol>\n')
+
+    def visit_error(self, node):
+        self.visit_admonition(node, 'error')
+
+    def depart_error(self, node):
+        self.depart_admonition()
+
+    def visit_field(self, node):
+        self.body.append(self.starttag(node, 'tr', '', CLASS='field'))
+
+    def depart_field(self, node):
+        self.body.append('</tr>\n')
+
+    def visit_field_body(self, node):
+        self.body.append(self.starttag(node, 'td', '', CLASS='field-body'))
+        self.set_first_last(node)
+
+    def depart_field_body(self, node):
+        self.body.append('</td>\n')
+
+    def visit_field_list(self, node):
+        self.body.append(self.starttag(node, 'table', frame='void',
+                                       rules='none',
+                                       CLASS='docutils field-list'))
+        self.body.append('<col class="field-name" />\n'
+                         '<col class="field-body" />\n'
+                         '<tbody valign="top">\n')
+
+    def depart_field_list(self, node):
+        self.body.append('</tbody>\n</table>\n')
+
+    def visit_field_name(self, node):
+        atts = {}
+        if self.in_docinfo:
+            atts['class'] = 'docinfo-name'
+        else:
+            atts['class'] = 'field-name'
+        if len(node.astext()) > 14:
+            atts['colspan'] = 2
+            self.context.append('</tr>\n<tr><td>&nbsp;</td>')
+        else:
+            self.context.append('')
+        self.body.append(self.starttag(node, 'th', '', **atts))
+
+    def depart_field_name(self, node):
+        self.body.append(':</th>')
+        self.body.append(self.context.pop())
+
+    def visit_figure(self, node):
+        atts = {'class': 'figure'}
+        if node.get('width'):
+            atts['style'] = 'width: %spx' % node['width']
+        self.body.append(self.starttag(node, 'div', **atts))
+
+    def depart_figure(self, node):
+        self.body.append('</div>\n')
+
+    def visit_footer(self, node):
+        self.context.append(len(self.body))
+
+    def depart_footer(self, node):
+        start = self.context.pop()
+        footer = (['<hr class="docutils footer" />\n',
+                   self.starttag(node, 'div', CLASS='footer')]
+                  + self.body[start:] + ['</div>\n'])
+        self.footer.extend(footer)
+        self.body_suffix[:0] = footer
+        del self.body[start:]
+
+    def visit_footnote(self, node):
+        self.body.append(self.starttag(node, 'table',
+                                       CLASS='docutils footnote',
+                                       frame="void", rules="none"))
+        self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
+                         '<tbody valign="top">\n'
+                         '<tr>')
+        self.footnote_backrefs(node)
+
+    def footnote_backrefs(self, node):
+        backlinks = []
+        if self.settings.footnote_backlinks and node.hasattr('backrefs'):
+            backrefs = node['backrefs']
+            if len(backrefs) == 1:
+                self.context.append('')
+                self.context.append('<a class="fn-backref" href="#%s" '
+                                    'name="%s">' % (backrefs[0], node['id']))
+            else:
+                i = 1
+                for backref in backrefs:
+                    backlinks.append('<a class="fn-backref" href="#%s">%s</a>'
+                                     % (backref, i))
+                    i += 1
+                self.context.append('<em>(%s)</em> ' % ', '.join(backlinks))
+                self.context.append('<a name="%s">' % node['id'])
+        else:
+            self.context.append('')
+            self.context.append('<a name="%s">' % node['id'])
+        # If the node does not only consist of a label.
+        if len(node) > 1:
+            # If there are preceding backlinks, we do not set class
+            # 'first', because we need to retain the top-margin.
+            if not backlinks:
+                node[1].set_class('first')
+            node[-1].set_class('last')
+
+    def depart_footnote(self, node):
+        self.body.append('</td></tr>\n'
+                         '</tbody>\n</table>\n')
+
+    def visit_footnote_reference(self, node):
+        href = ''
+        if node.has_key('refid'):
+            href = '#' + node['refid']
+        elif node.has_key('refname'):
+            href = '#' + self.document.nameids[node['refname']]
+        format = self.settings.footnote_references
+        if format == 'brackets':
+            suffix = '['
+            self.context.append(']')
+        elif format == 'superscript':
+            suffix = '<sup>'
+            self.context.append('</sup>')
+        else:                           # shouldn't happen
+            suffix = '???'
+            self.content.append('???')
+        self.body.append(self.starttag(node, 'a', suffix,
+                                       CLASS='footnote-reference',
+                                       **(href and {'href': href} or {})))
+
+    def depart_footnote_reference(self, node):
+        self.body.append(self.context.pop() + '</a>')
+
+    def visit_generated(self, node):
+        pass
+
+    def depart_generated(self, node):
+        pass
+
+    def visit_header(self, node):
+        self.context.append(len(self.body))
+
+    def depart_header(self, node):
+        start = self.context.pop()
+        header = [self.starttag(node, 'div', CLASS='header')]
+        header.extend(self.body[start:])
+        header.append('<hr class="docutils header"/>\n</div>\n')
+        self.body_prefix.extend(header)
+        self.header = header
+        del self.body[start:]
+
+    def visit_hint(self, node):
+        self.visit_admonition(node, 'hint')
+
+    def depart_hint(self, node):
+        self.depart_admonition()
+
+    def visit_image(self, node):
+        atts = node.attributes.copy()
+        if atts.has_key('class'):
+            del atts['class']           # prevent duplication with node attrs
+        atts['src'] = atts['uri']
+        del atts['uri']
+        if atts.has_key('scale'):
+            if Image and not (atts.has_key('width')
+                              and atts.has_key('height')):
+                try:
+                    im = Image.open(str(atts['src']))
+                except (IOError, # Source image can't be found or opened
+                        UnicodeError):  # PIL doesn't like Unicode paths.
+                    pass
+                else:
+                    if not atts.has_key('width'):
+                        atts['width'] = im.size[0]
+                    if not atts.has_key('height'):
+                        atts['height'] = im.size[1]
+                    del im
+            if atts.has_key('width'):
+                atts['width'] = int(round(atts['width']
+                                          * (float(atts['scale']) / 100)))
+            if atts.has_key('height'):
+                atts['height'] = int(round(atts['height']
+                                           * (float(atts['scale']) / 100)))
+            del atts['scale']
+        if not atts.has_key('alt'):
+            atts['alt'] = atts['src']
+        if isinstance(node.parent, nodes.TextElement):
+            self.context.append('')
+        else:
+            div_atts = self.image_div_atts(node)
+            self.body.append(self.starttag({}, 'div', '', **div_atts))
+            self.context.append('</div>\n')
+        self.body.append(self.emptytag(node, 'img', '', **atts))
+
+    def image_div_atts(self, image_node):
+        div_atts = {'class': 'image'}
+        if image_node.attributes.has_key('class'):
+            div_atts['class'] += ' ' + image_node.attributes['class']
+        if image_node.attributes.has_key('align'):
+            div_atts['align'] = self.attval(image_node.attributes['align'])
+            div_atts['class'] += ' align-%s' % div_atts['align']
+        return div_atts
+
+    def depart_image(self, node):
+        self.body.append(self.context.pop())
+
+    def visit_important(self, node):
+        self.visit_admonition(node, 'important')
+
+    def depart_important(self, node):
+        self.depart_admonition()
+
+    def visit_inline(self, node):
+        self.body.append(self.starttag(node, 'span', ''))
+
+    def depart_inline(self, node):
+        self.body.append('</span>')
+
+    def visit_label(self, node):
+        self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(),
+                                       CLASS='label'))
+
+    def depart_label(self, node):
+        self.body.append(']</a></td><td>%s' % self.context.pop())
+
+    def visit_legend(self, node):
+        self.body.append(self.starttag(node, 'div', CLASS='legend'))
+
+    def depart_legend(self, node):
+        self.body.append('</div>\n')
+
+    def visit_line(self, node):
+        self.body.append(self.starttag(node, 'div', suffix='', CLASS='line'))
+        if not len(node):
+            self.body.append('<br />')
+
+    def depart_line(self, node):
+        self.body.append('</div>\n')
+
+    def visit_line_block(self, node):
+        self.body.append(self.starttag(node, 'div', CLASS='line-block'))
+
+    def depart_line_block(self, node):
+        self.body.append('</div>\n')
+
+    def visit_list_item(self, node):
+        self.body.append(self.starttag(node, 'li', ''))
+        if len(node):
+            node[0].set_class('first')
+
+    def depart_list_item(self, node):
+        self.body.append('</li>\n')
+
+    def visit_literal(self, node):
+        """Process text to prevent tokens from wrapping."""
+        self.body.append(self.starttag(node, 'tt', '', CLASS='docutils literal'))
+        text = node.astext()
+        for token in self.words_and_spaces.findall(text):
+            if token.strip():
+                # Protect text like "--an-option" from bad line wrapping:
+                self.body.append('<span class="pre">%s</span>'
+                                 % self.encode(token))
+            elif token in ('\n', ' '):
+                # Allow breaks at whitespace:
+                self.body.append(token)
+            else:
+                # Protect runs of multiple spaces; the last space can wrap:
+                self.body.append('&nbsp;' * (len(token) - 1) + ' ')
+        self.body.append('</tt>')
+        # Content already processed:
+        raise nodes.SkipNode
+
+    def visit_literal_block(self, node):
+        self.body.append(self.starttag(node, 'pre', CLASS='literal-block'))
+
+    def depart_literal_block(self, node):
+        self.body.append('\n</pre>\n')
+
+    def visit_meta(self, node):
+        meta = self.emptytag(node, 'meta', **node.attributes)
+        self.add_meta(meta)
+
+    def depart_meta(self, node):
+        pass
+
+    def add_meta(self, tag):
+        self.meta.append(tag)
+        self.head.append(tag)
+
+    def visit_note(self, node):
+        self.visit_admonition(node, 'note')
+
+    def depart_note(self, node):
+        self.depart_admonition()
+
+    def visit_option(self, node):
+        if self.context[-1]:
+            self.body.append(', ')
+
+    def depart_option(self, node):
+        self.context[-1] += 1
+
+    def visit_option_argument(self, node):
+        self.body.append(node.get('delimiter', ' '))
+        self.body.append(self.starttag(node, 'var', ''))
+
+    def depart_option_argument(self, node):
+        self.body.append('</var>')
+
+    def visit_option_group(self, node):
+        atts = {}
+        if len(node.astext()) > 14:
+            atts['colspan'] = 2
+            self.context.append('</tr>\n<tr><td>&nbsp;</td>')
+        else:
+            self.context.append('')
+        self.body.append(self.starttag(node, 'td', **atts))
+        self.body.append('<kbd>')
+        self.context.append(0)          # count number of options
+
+    def depart_option_group(self, node):
+        self.context.pop()
+        self.body.append('</kbd></td>\n')
+        self.body.append(self.context.pop())
+
+    def visit_option_list(self, node):
+        self.body.append(
+              self.starttag(node, 'table', CLASS='docutils option-list',
+                            frame="void", rules="none"))
+        self.body.append('<col class="option" />\n'
+                         '<col class="description" />\n'
+                         '<tbody valign="top">\n')
+
+    def depart_option_list(self, node):
+        self.body.append('</tbody>\n</table>\n')
+
+    def visit_option_list_item(self, node):
+        self.body.append(self.starttag(node, 'tr', ''))
+
+    def depart_option_list_item(self, node):
+        self.body.append('</tr>\n')
+
+    def visit_option_string(self, node):
+        self.body.append(self.starttag(node, 'span', '', CLASS='option'))
+
+    def depart_option_string(self, node):
+        self.body.append('</span>')
+
+    def visit_organization(self, node):
+        self.visit_docinfo_item(node, 'organization')
+
+    def depart_organization(self, node):
+        self.depart_docinfo_item()
+
+    def should_be_compact_paragraph(self, node):
+        """
+        Determine if the <p> tags around paragraph ``node`` can be omitted.
+        """
+        if (isinstance(node.parent, nodes.document) or
+            isinstance(node.parent, nodes.compound)):
+            # Never compact paragraphs in document or compound.
+            return 0
+        if ((node.attributes in ({}, {'class': 'first'}, {'class': 'last'},
+                                 {'class': 'first last'})) and
+            (self.compact_simple or
+             self.compact_p and (len(node.parent) == 1 or
+                                 len(node.parent) == 2 and
+                                 isinstance(node.parent[0], nodes.label)))):
+            return 1
+        return 0
+
+    def visit_paragraph(self, node):
+        if self.should_be_compact_paragraph(node):
+            self.context.append('')
+        else:
+            self.body.append(self.starttag(node, 'p', ''))
+            self.context.append('</p>\n')
+
+    def depart_paragraph(self, node):
+        self.body.append(self.context.pop())
+
+    def visit_problematic(self, node):
+        if node.hasattr('refid'):
+            self.body.append('<a href="#%s" name="%s">' % (node['refid'],
+                                                           node['id']))
+            self.context.append('</a>')
+        else:
+            self.context.append('')
+        self.body.append(self.starttag(node, 'span', '', CLASS='problematic'))
+
+    def depart_problematic(self, node):
+        self.body.append('</span>')
+        self.body.append(self.context.pop())
+
+    def visit_raw(self, node):
+        if 'html' in node.get('format', '').split():
+            add_class = node.attributes.get('class') is not None
+            t = isinstance(node.parent, nodes.TextElement) and 'span' or 'div'
+            if add_class:
+                self.body.append(self.starttag(node, t, suffix=''))
+            self.body.append(node.astext())
+            if add_class:
+                self.body.append('</%s>' % t)
+        # Keep non-HTML raw text out of output:
+        raise nodes.SkipNode
+
+    def visit_reference(self, node):
+        if isinstance(node.parent, nodes.TextElement):
+            self.context.append('')
+        else:                           # contains an image
+            assert len(node) == 1 and isinstance(node[0], nodes.image)
+            div_atts = self.image_div_atts(node[0])
+            div_atts['class'] += ' image-reference'
+            self.body.append(self.starttag({}, 'div', '', **div_atts))
+            self.context.append('</div>\n')
+        href = ''
+        if node.has_key('refuri'):
+            href = node['refuri']
+        elif node.has_key('refid'):
+            href = '#' + node['refid']
+        elif node.has_key('refname'):
+            href = '#' + self.document.nameids[node['refname']]
+        self.body.append(self.starttag(node, 'a', '', CLASS='reference',
+                                       **(href and {'href': href} or {})))
+
+    def depart_reference(self, node):
+        self.body.append('</a>')
+        self.body.append(self.context.pop())
+
+    def visit_revision(self, node):
+        self.visit_docinfo_item(node, 'revision', meta=None)
+
+    def depart_revision(self, node):
+        self.depart_docinfo_item()
+
+    def visit_row(self, node):
+        self.body.append(self.starttag(node, 'tr', ''))
+
+    def depart_row(self, node):
+        self.body.append('</tr>\n')
+
+    def visit_rubric(self, node):
+        self.body.append(self.starttag(node, 'p', '', CLASS='rubric'))
+
+    def depart_rubric(self, node):
+        self.body.append('</p>\n')
+
+    def visit_section(self, node):
+        self.section_level += 1
+        self.body.append(self.starttag(node, 'div', CLASS='section'))
+
+    def depart_section(self, node):
+        self.section_level -= 1
+        self.body.append('</div>\n')
+
+    def visit_sidebar(self, node):
+        self.body.append(self.starttag(node, 'div', CLASS='sidebar'))
+        self.set_first_last(node)
+        self.in_sidebar = 1
+
+    def depart_sidebar(self, node):
+        self.body.append('</div>\n')
+        self.in_sidebar = None
+
+    def visit_status(self, node):
+        self.visit_docinfo_item(node, 'status', meta=None)
+
+    def depart_status(self, node):
+        self.depart_docinfo_item()
+
+    def visit_strong(self, node):
+        self.body.append('<strong>')
+
+    def depart_strong(self, node):
+        self.body.append('</strong>')
+
+    def visit_subscript(self, node):
+        self.body.append(self.starttag(node, 'sub', ''))
+
+    def depart_subscript(self, node):
+        self.body.append('</sub>')
+
+    def visit_substitution_definition(self, node):
+        """Internal only."""
+        raise nodes.SkipNode
+
+    def visit_substitution_reference(self, node):
+        self.unimplemented_visit(node)
+
+    def visit_subtitle(self, node):
+        if isinstance(node.parent, nodes.sidebar):
+            self.body.append(self.starttag(node, 'p', '',
+                                           CLASS='sidebar-subtitle'))
+            self.context.append('</p>\n')
+        elif isinstance(node.parent, nodes.document):
+            self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle'))
+            self.context.append('</h2>\n')
+            self.in_document_title = len(self.body)
+
+    def depart_subtitle(self, node):
+        self.body.append(self.context.pop())
+        if self.in_document_title:
+            self.subtitle = self.body[self.in_document_title:-1]
+            self.in_document_title = 0
+            self.body_pre_docinfo.extend(self.body)
+            del self.body[:]
+
+    def visit_superscript(self, node):
+        self.body.append(self.starttag(node, 'sup', ''))
+
+    def depart_superscript(self, node):
+        self.body.append('</sup>')
+
+    def visit_system_message(self, node):
+        if node['level'] < self.document.reporter['writer'].report_level:
+            # Level is too low to display:
+            raise nodes.SkipNode
+        self.body.append(self.starttag(node, 'div', CLASS='system-message'))
+        self.body.append('<p class="system-message-title">')
+        attr = {}
+        backref_text = ''
+        if node.hasattr('id'):
+            attr['name'] = node['id']
+        if node.hasattr('backrefs'):
+            backrefs = node['backrefs']
+            if len(backrefs) == 1:
+                backref_text = ('; <em><a href="#%s">backlink</a></em>'
+                                % backrefs[0])
+            else:
+                i = 1
+                backlinks = []
+                for backref in backrefs:
+                    backlinks.append('<a href="#%s">%s</a>' % (backref, i))
+                    i += 1
+                backref_text = ('; <em>backlinks: %s</em>'
+                                % ', '.join(backlinks))
+        if node.hasattr('line'):
+            line = ', line %s' % node['line']
+        else:
+            line = ''
+        if attr:
+            a_start = self.starttag({}, 'a', '', **attr)
+            a_end = '</a>'
+        else:
+            a_start = a_end = ''
+        self.body.append('System Message: %s%s/%s%s '
+                         '(<tt class="docutils">%s</tt>%s)%s</p>\n'
+                         % (a_start, node['type'], node['level'], a_end,
+                            self.encode(node['source']), line, backref_text))
+
+    def depart_system_message(self, node):
+        self.body.append('</div>\n')
+
+    def visit_table(self, node):
+        self.body.append(
+            self.starttag(node, 'table', CLASS='docutils', border="1"))
+
+    def depart_table(self, node):
+        self.body.append('</table>\n')
+
+    def visit_target(self, node):
+        if not (node.has_key('refuri') or node.has_key('refid')
+                or node.has_key('refname')):
+            self.body.append(self.starttag(node, 'a', '', CLASS='target'))
+            self.context.append('</a>')
+        else:
+            self.context.append('')
+
+    def depart_target(self, node):
+        self.body.append(self.context.pop())
+
+    def visit_tbody(self, node):
+        self.write_colspecs()
+        self.body.append(self.context.pop()) # '</colgroup>\n' or ''
+        self.body.append(self.starttag(node, 'tbody', valign='top'))
+
+    def depart_tbody(self, node):
+        self.body.append('</tbody>\n')
+
+    def visit_term(self, node):
+        self.body.append(self.starttag(node, 'dt', ''))
+
+    def depart_term(self, node):
+        """
+        Leave the end tag to `self.visit_definition()`, in case there's a
+        classifier.
+        """
+        pass
+
+    def visit_tgroup(self, node):
+        # Mozilla needs <colgroup>:
+        self.body.append(self.starttag(node, 'colgroup'))
+        # Appended by thead or tbody:
+        self.context.append('</colgroup>\n')
+
+    def depart_tgroup(self, node):
+        pass
+
+    def visit_thead(self, node):
+        self.write_colspecs()
+        self.body.append(self.context.pop()) # '</colgroup>\n'
+        # There may or may not be a <thead>; this is for <tbody> to use:
+        self.context.append('')
+        self.body.append(self.starttag(node, 'thead', valign='bottom'))
+
+    def depart_thead(self, node):
+        self.body.append('</thead>\n')
+
+    def visit_tip(self, node):
+        self.visit_admonition(node, 'tip')
+
+    def depart_tip(self, node):
+        self.depart_admonition()
+
+    def visit_title(self, node):
+        """Only 6 section levels are supported by HTML."""
+        check_id = 0
+        close_tag = '</p>\n'
+        if isinstance(node.parent, nodes.topic):
+            self.body.append(
+                  self.starttag(node, 'p', '', CLASS='topic-title first'))
+            check_id = 1
+        elif isinstance(node.parent, nodes.sidebar):
+            self.body.append(
+                  self.starttag(node, 'p', '', CLASS='sidebar-title'))
+            check_id = 1
+        elif isinstance(node.parent, nodes.Admonition):
+            self.body.append(
+                  self.starttag(node, 'p', '', CLASS='admonition-title'))
+            check_id = 1
+        elif isinstance(node.parent, nodes.table):
+            self.body.append(
+                  self.starttag(node, 'caption', ''))
+            check_id = 1
+            close_tag = '</caption>\n'
+        elif self.section_level == 0:
+            # document title
+            self.head.append('<title>%s</title>\n'
+                             % self.encode(node.astext()))
+            self.body.append(self.starttag(node, 'h1', '', CLASS='title'))
+            self.context.append('</h1>\n')
+            self.in_document_title = len(self.body)
+        else:
+            h_level = self.section_level + self.initial_header_level - 1
+            self.body.append(
+                  self.starttag(node, 'h%s' % h_level, ''))
+            atts = {}
+            if node.parent.hasattr('id'):
+                atts['name'] = node.parent['id']
+            if node.hasattr('refid'):
+                atts['class'] = 'toc-backref'
+                atts['href'] = '#' + node['refid']
+            self.body.append(self.starttag({}, 'a', '', **atts))
+            self.context.append('</a></h%s>\n' % (h_level))
+        if check_id:
+            if node.parent.hasattr('id'):
+                self.body.append(
+                    self.starttag({}, 'a', '', name=node.parent['id']))
+                self.context.append('</a>' + close_tag)
+            else:
+                self.context.append(close_tag)
+
+    def depart_title(self, node):
+        self.body.append(self.context.pop())
+        if self.in_document_title:
+            self.title = self.body[self.in_document_title:-1]
+            self.in_document_title = 0
+            self.body_pre_docinfo.extend(self.body)
+            del self.body[:]
+
+    def visit_title_reference(self, node):
+        self.body.append(self.starttag(node, 'cite', ''))
+
+    def depart_title_reference(self, node):
+        self.body.append('</cite>')
+
+    def visit_topic(self, node):
+        self.body.append(self.starttag(node, 'div', CLASS='topic'))
+        self.topic_class = node.get('class')
+
+    def depart_topic(self, node):
+        self.body.append('</div>\n')
+        self.topic_class = ''
+
+    def visit_transition(self, node):
+        self.body.append(self.emptytag(node, 'hr', CLASS='docutils'))
+
+    def depart_transition(self, node):
+        pass
+
+    def visit_version(self, node):
+        self.visit_docinfo_item(node, 'version', meta=None)
+
+    def depart_version(self, node):
+        self.depart_docinfo_item()
+
+    def visit_warning(self, node):
+        self.visit_admonition(node, 'warning')
+
+    def depart_warning(self, node):
+        self.depart_admonition()
+
+    def unimplemented_visit(self, node):
+        raise NotImplementedError('visiting unimplemented node type: %s'
+                                  % node.__class__.__name__)
+
+
+class SimpleListChecker(nodes.GenericNodeVisitor):
+
+    """
+    Raise `nodes.NodeFound` if non-simple list item is encountered.
+
+    Here "simple" means a list item containing nothing other than a single
+    paragraph, a simple list, or a paragraph followed by a simple list.
+    """
+
+    def default_visit(self, node):
+        raise nodes.NodeFound
+
+    def visit_bullet_list(self, node):
+        pass
+
+    def visit_enumerated_list(self, node):
+        pass
+
+    def visit_list_item(self, node):
+        children = []
+        for child in node.get_children():
+            if not isinstance(child, nodes.Invisible):
+                children.append(child)
+        if (children and isinstance(children[0], nodes.paragraph)
+            and (isinstance(children[-1], nodes.bullet_list)
+                 or isinstance(children[-1], nodes.enumerated_list))):
+            children.pop()
+        if len(children) <= 1:
+            return
+        else:
+            raise nodes.NodeFound
+
+    def visit_paragraph(self, node):
+        raise nodes.SkipNode
+
+    def invisible_visit(self, node):
+        """Invisible nodes should be ignored."""
+        raise nodes.SkipNode
+
+    visit_comment = invisible_visit
+    visit_substitution_definition = invisible_visit
+    visit_target = invisible_visit
+    visit_pending = invisible_visit

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/latex2e.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/latex2e.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/latex2e.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,2011 @@
+"""
+:Author: Engelbert Gruber
+:Contact: grubert at users.sourceforge.net
+:Revision: $Revision: 1.1.2.7 $
+:Date: $Date: 2005/01/07 13:26:06 $
+:Copyright: This module has been placed in the public domain.
+
+LaTeX2e document tree Writer.
+"""
+
+__docformat__ = 'reStructuredText'
+
+# code contributions from several people included, thanks to all.
+# some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
+#
+# convention deactivate code by two # e.g. ##.
+
+import sys
+import time
+import re
+import string
+from types import ListType
+from docutils import frontend, nodes, languages, writers, utils
+
+class Writer(writers.Writer):
+
+    supported = ('latex','latex2e')
+    """Formats this writer supports."""
+
+    settings_spec = (
+        'LaTeX-Specific Options',
+        'The LaTeX "--output-encoding" default is "latin-1:strict".',
+        (('Specify documentclass.  Default is "article".',
+          ['--documentclass'],
+          {'default': 'article', }),
+         ('Specify document options.  Multiple options can be given, '
+          'separated by commas.  Default is "10pt,a4paper".',
+          ['--documentoptions'],
+          {'default': '10pt,a4paper', }),
+         ('Use LaTeX footnotes. LaTeX supports only numbered footnotes (does it?). '
+          'Default: no, uses figures.',
+          ['--use-latex-footnotes'],
+          {'default': 0, 'action': 'store_true',
+           'validator': frontend.validate_boolean}),
+         ('Format for footnote references: one of "superscript" or '
+          '"brackets".  Default is "superscript".',
+          ['--footnote-references'],
+          {'choices': ['superscript', 'brackets'], 'default': 'superscript',
+           'metavar': '<format>',
+           'overrides': 'trim_footnote_reference_space'}),
+         ('Use LaTeX citations. '
+          'Default: no, uses figures which might get mixed with images.',
+          ['--use-latex-citations'],
+          {'default': 0, 'action': 'store_true',
+           'validator': frontend.validate_boolean}),
+         ('Format for block quote attributions: one of "dash" (em-dash '
+          'prefix), "parentheses"/"parens", or "none".  Default is "dash".',
+          ['--attribution'],
+          {'choices': ['dash', 'parentheses', 'parens', 'none'],
+           'default': 'dash', 'metavar': '<format>'}),
+         ('Specify a stylesheet file. The file will be "input" by latex in '
+          'the document header.  Default is no stylesheet ("").  '
+          'Overrides --stylesheet-path.',
+          ['--stylesheet'],
+          {'default': '', 'metavar': '<file>',
+           'overrides': 'stylesheet_path'}),
+         ('Specify a stylesheet file, relative to the current working '
+          'directory.  Overrides --stylesheet.',
+          ['--stylesheet-path'],
+          {'metavar': '<file>', 'overrides': 'stylesheet'}),
+         ('Table of contents by docutils (default) or latex. Latex (writer) '
+          'supports only one ToC per document, but docutils does not write '
+          'pagenumbers.',
+          ['--use-latex-toc'],
+          {'default': 0, 'action': 'store_true',
+           'validator': frontend.validate_boolean}),
+         ('Let LaTeX print author and date, do not show it in docutils '
+          'document info.',
+          ['--use-latex-docinfo'],
+          {'default': 0, 'action': 'store_true',
+           'validator': frontend.validate_boolean}),
+         ('Color of any hyperlinks embedded in text '
+          '(default: "blue", "0" to disable).',
+          ['--hyperlink-color'], {'default': 'blue'}),
+         ('Enable compound enumerators for nested enumerated lists '
+          '(e.g. "1.2.a.ii").  Default: disabled.',
+          ['--compound-enumerators'],
+          {'default': None, 'action': 'store_true',
+           'validator': frontend.validate_boolean}),
+         ('Disable compound enumerators for nested enumerated lists.  This is '
+          'the default.',
+          ['--no-compound-enumerators'],
+          {'action': 'store_false', 'dest': 'compound_enumerators'}),
+         ('Enable section ("." subsection ...) prefixes for compound '
+          'enumerators.  This has no effect without --compound-enumerators.  '
+          'Default: disabled.',
+          ['--section-prefix-for-enumerators'],
+          {'default': None, 'action': 'store_true',
+           'validator': frontend.validate_boolean}),
+         ('Disable section prefixes for compound enumerators.  '
+          'This is the default.',
+          ['--no-section-prefix-for-enumerators'],
+          {'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}),
+         ('Set the separator between section number and enumerator '
+          'for compound enumerated lists.  Default is "-".',
+          ['--section-enumerator-separator'],
+          {'default': '-', 'metavar': '<char>'}),
+         ('When possibile, use verbatim for literal-blocks. '
+          'Default is to always use the mbox environment.',
+          ['--use-verbatim-when-possible'],
+          {'default': 0, 'action': 'store_true',
+           'validator': frontend.validate_boolean}),
+         ('Table style. "standard" with horizontal and vertical lines, '
+          '"booktabs" (LaTeX booktabs style) only horizontal lines '
+          'above and below the table and below the header or "nolines".  '
+          'Default: "standard"',
+          ['--table-style'],
+          {'choices': ['standard', 'booktabs','nolines'], 'default': 'standard',
+           'metavar': '<format>'}),
+         ('LaTeX graphicx package option. '
+          'Possible values are "dvips", "pdftex". "auto" includes LaTeX code '
+          'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. '
+          'Default is no option.',
+          ['--graphicx-option'],
+          {'default': ''}),
+         ('LaTeX font encoding. '
+          'Possible values are "T1", "OT1", "" or some other fontenc option. '
+          'The font encoding influences available symbols, e.g. "<<" as one '
+          'character. Default is "" which leads to package "ae" (a T1 '
+          'emulation using CM fonts).',
+          ['--font-encoding'],
+          {'default': ''}),
+          ),)
+
+    settings_defaults = {'output_encoding': 'latin-1'}
+
+    relative_path_settings = ('stylesheet_path',)
+
+    config_section = 'latex2e writer'
+    config_section_dependencies = ('writers',)
+
+    output = None
+    """Final translated form of `document`."""
+
+    def __init__(self):
+        writers.Writer.__init__(self)
+        self.translator_class = LaTeXTranslator
+
+    def translate(self):
+        visitor = self.translator_class(self.document)
+        self.document.walkabout(visitor)
+        self.output = visitor.astext()
+        self.head_prefix = visitor.head_prefix
+        self.head = visitor.head
+        self.body_prefix = visitor.body_prefix
+        self.body = visitor.body
+        self.body_suffix = visitor.body_suffix
+
+"""
+Notes on LaTeX
+--------------
+
+* latex does not support multiple tocs in one document.
+  (might be no limitation except for docutils documentation)
+
+* width
+
+  * linewidth - width of a line in the local environment
+  * textwidth - the width of text on the page
+
+  Maybe always use linewidth ?
+
+  *Bug* inside a minipage a (e.g. Sidebar) the linewidth is
+        not changed, needs fix in docutils so that tables
+        are not too wide.
+
+        So we add locallinewidth set it initially and
+        on entering sidebar and reset on exit.
+"""
+
+class Babel:
+    """Language specifics for LaTeX."""
+    # country code by a.schlock.
+    # partly manually converted from iso and babel stuff, dialects and some
+    _ISO639_TO_BABEL = {
+        'no': 'norsk',     #XXX added by hand ( forget about nynorsk?)
+        'gd': 'scottish',  #XXX added by hand
+        'hu': 'magyar',    #XXX added by hand
+        'pt': 'portuguese',#XXX added by hand
+        'sl': 'slovenian',
+        'af': 'afrikaans',
+        'bg': 'bulgarian',
+        'br': 'breton',
+        'ca': 'catalan',
+        'cs': 'czech',
+        'cy': 'welsh',
+        'da': 'danish',
+        'fr': 'french',
+        # french, francais, canadien, acadian
+        'de': 'ngerman',  #XXX rather than german
+        # ngerman, naustrian, german, germanb, austrian
+        'el': 'greek',
+        'en': 'english',
+        # english, USenglish, american, UKenglish, british, canadian
+        'eo': 'esperanto',
+        'es': 'spanish',
+        'et': 'estonian',
+        'eu': 'basque',
+        'fi': 'finnish',
+        'ga': 'irish',
+        'gl': 'galician',
+        'he': 'hebrew',
+        'hr': 'croatian',
+        'hu': 'hungarian',
+        'is': 'icelandic',
+        'it': 'italian',
+        'la': 'latin',
+        'nl': 'dutch',
+        'pl': 'polish',
+        'pt': 'portuguese',
+        'ro': 'romanian',
+        'ru': 'russian',
+        'sk': 'slovak',
+        'sr': 'serbian',
+        'sv': 'swedish',
+        'tr': 'turkish',
+        'uk': 'ukrainian'
+    }
+
+    def __init__(self,lang):
+        self.language = lang
+        # pdflatex does not produce double quotes for ngerman in tt.
+        self.double_quote_replacment = None
+        if re.search('^de',self.language):
+            #self.quotes = ("\"`", "\"'")
+            self.quotes = ('{\\glqq}', '{\\grqq}')
+            self.double_quote_replacment = "{\\dq}"
+        else:
+            self.quotes = ("``", "''")
+        self.quote_index = 0
+
+    def next_quote(self):
+        q = self.quotes[self.quote_index]
+        self.quote_index = (self.quote_index+1)%2
+        return q
+
+    def quote_quotes(self,text):
+        t = None
+        for part in text.split('"'):
+            if t == None:
+                t = part
+            else:
+                t += self.next_quote() + part
+        return t
+
+    def double_quotes_in_tt (self,text):
+        if not self.double_quote_replacment:
+            return text
+        return text.replace('"', self.double_quote_replacment)
+
+    def get_language(self):
+        if self._ISO639_TO_BABEL.has_key(self.language):
+            return self._ISO639_TO_BABEL[self.language]
+        else:
+            # support dialects.
+            l = self.language.split("_")[0]
+            if self._ISO639_TO_BABEL.has_key(l):
+                return self._ISO639_TO_BABEL[l]
+        return None
+
+
+latex_headings = {
+        'optionlist_environment' : [
+              '\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
+              '\\newenvironment{optionlist}[1]\n'
+              '{\\begin{list}{}\n'
+              '  {\\setlength{\\labelwidth}{#1}\n'
+              '   \\setlength{\\rightmargin}{1cm}\n'
+              '   \\setlength{\\leftmargin}{\\rightmargin}\n'
+              '   \\addtolength{\\leftmargin}{\\labelwidth}\n'
+              '   \\addtolength{\\leftmargin}{\\labelsep}\n'
+              '   \\renewcommand{\\makelabel}{\\optionlistlabel}}\n'
+              '}{\\end{list}}\n',
+              ],
+        'lineblock_environment' : [
+            '\\newlength{\\lineblockindentation}\n'
+            '\\setlength{\\lineblockindentation}{2.5em}\n'
+            '\\newenvironment{lineblock}[1]\n'
+            '{\\begin{list}{}\n'
+            '  {\\setlength{\\partopsep}{\\parskip}\n'
+            '   \\addtolength{\\partopsep}{\\baselineskip}\n'
+            '   \\topsep0pt\\itemsep0.15\\baselineskip\\parsep0pt\n'
+            '   \\leftmargin#1}\n'
+            ' \\raggedright}\n'
+            '{\\end{list}}\n'
+            ],
+        'footnote_floats' : [
+            '% begin: floats for footnotes tweaking.\n',
+            '\\setlength{\\floatsep}{0.5em}\n',
+            '\\setlength{\\textfloatsep}{\\fill}\n',
+            '\\addtolength{\\textfloatsep}{3em}\n',
+            '\\renewcommand{\\textfraction}{0.5}\n',
+            '\\renewcommand{\\topfraction}{0.5}\n',
+            '\\renewcommand{\\bottomfraction}{0.5}\n',
+            '\\setcounter{totalnumber}{50}\n',
+            '\\setcounter{topnumber}{50}\n',
+            '\\setcounter{bottomnumber}{50}\n',
+            '% end floats for footnotes\n',
+            ],
+        'some_commands' : [
+            '% some commands, that could be overwritten in the style file.\n'
+            '\\newcommand{\\rubric}[1]'
+            '{\\subsection*{~\\hfill {\\it #1} \\hfill ~}}\n'
+            '\\newcommand{\\titlereference}[1]{\\textsl{#1}}\n'
+            '% end of "some commands"\n',
+            ]
+        }
+
+class DocumentClass:
+    """Details of a LaTeX document class."""
+
+    # BUG: LaTeX has no deeper sections (actually paragrah is no
+    # section either).
+    # BUG: No support for unknown document classes.  Make 'article'
+    # default?
+    _class_sections = {
+        'book': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
+        'scrbook': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
+        'report': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
+        'scrreprt': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
+        'article': ( 'section', 'subsection', 'subsubsection' ),
+        'scrartcl': ( 'section', 'subsection', 'subsubsection' ),
+        }
+    _deepest_section = 'subsubsection'
+
+    def __init__(self, document_class):
+        self.document_class = document_class
+
+    def section(self, level):
+        """ Return the section name at the given level for the specific
+            document class.
+
+            Level is 1,2,3..., as level 0 is the title."""
+
+        sections = self._class_sections[self.document_class]
+        if level <= len(sections):
+            return sections[level-1]
+        else:
+            return self._deepest_section
+
+class Table:
+    """ Manage a table while traversing. 
+        Maybe change to a mixin defining the visit/departs, but then
+        class Table internal variables are in the Translator.
+    """
+    def __init__(self,latex_type,table_style):
+        self._latex_type = latex_type
+        self._table_style = table_style
+        self._open = 0
+        # miscellaneous attributes
+        self._attrs = {}
+        self._col_width = []
+        self._rowspan = []
+
+    def open(self):
+        self._open = 1
+        self._col_specs = []
+        self.caption = None
+        self._attrs = {}
+        self._in_head = 0 # maybe context with search
+    def close(self):
+        self._open = 0
+        self._col_specs = None
+        self.caption = None
+        self._attrs = {}
+    def is_open(self):
+        return self._open
+    def used_packages(self):
+        if self._table_style == 'booktabs':
+            return '\\usepackage{booktabs}\n'
+        return ''
+    def get_latex_type(self):
+        return self._latex_type
+    
+    def set(self,attr,value):
+        self._attrs[attr] = value
+    def get(self,attr):
+        if self._attrs.has_key(attr):
+            return self._attrs[attr]
+        return None
+    def get_vertical_bar(self):
+        if self._table_style == 'standard':
+            return '|'
+        return ''
+    # horizontal lines are drawn below a row, because we.
+    def get_opening(self):
+        return '\\begin{%s}[c]' % self._latex_type
+    def get_closing(self):
+        line = ""
+        if self._table_style == 'booktabs':
+            line = '\\bottomrule\n'
+        elif self._table_style == 'standard':
+            lines = '\\hline\n'
+        return '%s\\end{%s}' % (line,self._latex_type)
+
+    def visit_colspec(self,node):
+        self._col_specs.append(node)
+
+    def get_colspecs(self):
+        """
+        Return column specification for longtable.
+
+        Assumes reST line length being 80 characters.
+        Table width is hairy.
+
+        === ===
+        ABC DEF
+        === ===
+
+        usually gets to narrow, therefore we add 1 (fiddlefactor).
+        """
+        width = 80
+
+        total_width = 0.0
+        # first see if we get too wide.
+        for node in self._col_specs:
+            colwidth = float(node['colwidth']+1) / width
+            total_width += colwidth
+        self._col_width = []
+        self._rowspan = []
+        # donot make it full linewidth
+        factor = 0.93
+        if total_width > 1.0:
+            factor /= total_width
+        bar = self.get_vertical_bar()
+        latex_table_spec = ""
+        for node in self._col_specs:
+            colwidth = factor * float(node['colwidth']+1) / width
+            self._col_width.append(colwidth+0.005)
+            self._rowspan.append(0)
+            latex_table_spec += "%sp{%.2f\\locallinewidth}" % (bar,colwidth+0.005)
+        return latex_table_spec+bar
+
+    def get_column_width(self):
+        """ return columnwidth for current cell (not multicell) 
+        """
+        return "%.2f\\locallinewidth" % self._col_width[self._cell_in_row-1]
+
+    def visit_thead(self):
+        self._in_thead = 1
+        if self._table_style == 'standard':
+            return ['\\hline\n']
+        elif self._table_style == 'booktabs':
+            return ['\\toprule\n']
+        return []
+    def depart_thead(self):
+        a = []
+        #if self._table_style == 'standard':
+        #    a.append('\\hline\n')
+        if self._table_style == 'booktabs':
+            a.append('\\midrule\n')
+        a.append('\\endhead\n')
+        # for longtable one could add firsthead, foot and lastfoot
+        self._in_thead = 0
+        return a
+    def visit_row(self):
+        self._cell_in_row = 0
+    def depart_row(self):
+        res = [' \\\\\n']
+        self._cell_in_row = None  # remove cell counter
+        for i in range(len(self._rowspan)):
+            if (self._rowspan[i]>0):
+                self._rowspan[i] -= 1
+        
+        if self._table_style == 'standard':
+            rowspans = []
+            for i in range(len(self._rowspan)):
+                if (self._rowspan[i]<=0):
+                    rowspans.append(i+1)
+            if len(rowspans)==len(self._rowspan):
+                res.append('\\hline\n')
+            else:
+                cline = ''
+                rowspans.reverse()
+                # TODO merge clines
+                while 1:
+                    try:
+                        c_start = rowspans.pop()
+                    except:
+                        break
+                    cline += '\\cline{%d-%d}\n' % (c_start,c_start)
+                res.append(cline)
+        return res
+
+    def set_rowspan(self,cell,value):
+        try:
+            self._rowspan[cell] = value
+        except:
+            pass
+    def get_rowspan(self,cell):
+        try:
+            return self._rowspan[cell]
+        except:
+            return 0
+    def get_entry_number(self):
+        return self._cell_in_row
+    def visit_entry(self):
+        self._cell_in_row += 1
+
+        
+class LaTeXTranslator(nodes.NodeVisitor):
+
+    # When options are given to the documentclass, latex will pass them
+    # to other packages, as done with babel.
+    # Dummy settings might be taken from document settings
+
+    latex_head = '\\documentclass[%s]{%s}\n'
+    encoding = '\\usepackage[%s]{inputenc}\n'
+    linking = '\\usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}\n'
+    stylesheet = '\\input{%s}\n'
+    # add a generated on day , machine by user using docutils version.
+    generator = '%% generator Docutils: http://docutils.sourceforge.net/\n'
+
+    # use latex tableofcontents or let docutils do it.
+    use_latex_toc = 0
+
+    # TODO: use mixins for different implementations.
+    # list environment for option-list. else tabularx
+    use_optionlist_for_option_list = 1
+    # list environment for docinfo. else tabularx
+    use_optionlist_for_docinfo = 0 # NOT YET IN USE
+
+    # Use compound enumerations (1.A.1.)
+    compound_enumerators = 0
+
+    # If using compound enumerations, include section information.
+    section_prefix_for_enumerators = 0
+
+    # This is the character that separates the section ("." subsection ...)
+    # prefix from the regular list enumerator.
+    section_enumerator_separator = '-'
+
+    # default link color
+    hyperlink_color = "blue"
+
+    def __init__(self, document):
+        nodes.NodeVisitor.__init__(self, document)
+        self.settings = settings = document.settings
+        self.latex_encoding = self.to_latex_encoding(settings.output_encoding)
+        self.use_latex_toc = settings.use_latex_toc
+        self.use_latex_docinfo = settings.use_latex_docinfo
+        self.use_latex_footnotes = settings.use_latex_footnotes
+        self._use_latex_citations = settings.use_latex_citations
+        self.hyperlink_color = settings.hyperlink_color
+        self.compound_enumerators = settings.compound_enumerators
+        self.font_encoding = settings.font_encoding
+        self.section_prefix_for_enumerators = (
+            settings.section_prefix_for_enumerators)
+        self.section_enumerator_separator = (
+            settings.section_enumerator_separator.replace('_', '\\_'))
+        if self.hyperlink_color == '0':
+            self.hyperlink_color = 'black'
+            self.colorlinks = 'false'
+        else:
+            self.colorlinks = 'true'
+
+        # language: labels, bibliographic_fields, and author_separators.
+        # to allow writing labes for specific languages.
+        self.language = languages.get_language(settings.language_code)
+        self.babel = Babel(settings.language_code)
+        self.author_separator = self.language.author_separators[0]
+        self.d_options = self.settings.documentoptions
+        if self.babel.get_language():
+            self.d_options += ',%s' % \
+                    self.babel.get_language()
+
+        self.d_class = DocumentClass(settings.documentclass)
+        # object for a table while proccessing.
+        self.active_table = Table('longtable',settings.table_style)
+
+        # HACK.  Should have more sophisticated typearea handling.
+        if settings.documentclass.find('scr') == -1:
+            self.typearea = '\\usepackage[DIV12]{typearea}\n'
+        else:
+            if self.d_options.find('DIV') == -1 and self.d_options.find('BCOR') == -1:
+                self.typearea = '\\typearea{12}\n'
+            else:
+                self.typearea = ''
+
+        if self.font_encoding == 'OT1':
+            fontenc_header = ''
+        elif self.font_encoding == '':
+            fontenc_header = '\\usepackage{ae}\n\\usepackage{aeguill}\n'
+        else:
+            fontenc_header = '\\usepackage[%s]{fontenc}\n' % (self.font_encoding,)
+        input_encoding = self.encoding % self.latex_encoding
+        if self.settings.graphicx_option == '':
+            self.graphicx_package = '\\usepackage{graphicx}\n'
+        elif self.settings.graphicx_option.lower() == 'auto':
+            self.graphicx_package = '\n'.join(
+                ('%Check if we are compiling under latex or pdflatex',
+                 '\\ifx\\pdftexversion\\undefined',
+                 '  \\usepackage{graphicx}',
+                 '\\else',
+                 '  \\usepackage[pdftex]{graphicx}',
+                 '\\fi\n'))
+        else:
+            self.graphicx_package = (
+                '\\usepackage[%s]{graphicx}\n' % self.settings.graphicx_option)
+
+        self.head_prefix = [
+              self.latex_head % (self.d_options,self.settings.documentclass),
+              '\\usepackage{babel}\n',     # language is in documents settings.
+              fontenc_header,
+              '\\usepackage{shortvrb}\n',  # allows verb in footnotes.
+              input_encoding,
+              # * tabularx: for docinfo, automatic width of columns, always on one page.
+              '\\usepackage{tabularx}\n',
+              '\\usepackage{longtable}\n',
+              self.active_table.used_packages(),
+              # possible other packages.
+              # * fancyhdr
+              # * ltxtable is a combination of tabularx and longtable (pagebreaks).
+              #   but ??
+              #
+              # extra space between text in tables and the line above them
+              '\\setlength{\\extrarowheight}{2pt}\n',
+              '\\usepackage{amsmath}\n',   # what fore amsmath.
+              self.graphicx_package,
+              '\\usepackage{color}\n',
+              '\\usepackage{multirow}\n',
+              '\\usepackage{ifthen}\n',   # before hyperref!
+              self.linking % (self.colorlinks, self.hyperlink_color, self.hyperlink_color),
+              self.typearea,
+              self.generator,
+              # latex lengths
+              '\\newlength{\\admonitionwidth}\n',
+              '\\setlength{\\admonitionwidth}{0.9\\textwidth}\n'
+              # width for docinfo tablewidth
+              '\\newlength{\\docinfowidth}\n',
+              '\\setlength{\\docinfowidth}{0.9\\textwidth}\n'
+              # linewidth of current environment, so tables are not wider
+              # than the sidebar: using locallinewidth seems to defer evaluation
+              # of linewidth, this is fixing it.
+              '\\newlength{\\locallinewidth}\n',
+              # will be set later.
+              ]
+        self.head_prefix.extend( latex_headings['optionlist_environment'] )
+        self.head_prefix.extend( latex_headings['lineblock_environment'] )
+        self.head_prefix.extend( latex_headings['footnote_floats'] )
+        self.head_prefix.extend( latex_headings['some_commands'] )
+        ## stylesheet is last: so it might be possible to overwrite defaults.
+        stylesheet = utils.get_stylesheet_reference(settings)
+        if stylesheet:
+            settings.record_dependencies.add(stylesheet)
+            self.head_prefix.append(self.stylesheet % (stylesheet))
+
+        if self.linking: # and maybe check for pdf
+            self.pdfinfo = [ ]
+            self.pdfauthor = None
+            # pdftitle, pdfsubject, pdfauthor, pdfkeywords, pdfcreator, pdfproducer
+        else:
+            self.pdfinfo = None
+        # NOTE: Latex wants a date and an author, rst puts this into
+        #   docinfo, so normally we donot want latex author/date handling.
+        # latex article has its own handling of date and author, deactivate.
+        self.head = [ ]
+        if not self.use_latex_docinfo:
+            self.head.extend( [ '\\author{}\n', '\\date{}\n' ] )
+        self.body_prefix = ['\\raggedbottom\n']
+        # separate title, so we can appen subtitle.
+        self.title = ""
+        self.body = []
+        self.body_suffix = ['\n']
+        self.section_level = 0
+        self.context = []
+        self.topic_class = ''
+        # column specification for tables
+        self.table_caption = None
+        # do we have one or more authors
+        self.author_stack = None
+        # Flags to encode
+        # ---------------
+        # verbatim: to tell encode not to encode.
+        self.verbatim = 0
+        # insert_newline: to tell encode to replace blanks by "~".
+        self.insert_none_breaking_blanks = 0
+        # insert_newline: to tell encode to add latex newline.
+        self.insert_newline = 0
+        # mbox_newline: to tell encode to add mbox and newline.
+        self.mbox_newline = 0
+
+        # enumeration is done by list environment.
+        self._enum_cnt = 0
+
+        # Stack of section counters so that we don't have to use_latex_toc.
+        # This will grow and shrink as processing occurs.
+        # Initialized for potential first-level sections.
+        self._section_number = [0]
+
+        # The current stack of enumerations so that we can expand
+        # them into a compound enumeration
+        self._enumeration_counters = []
+
+        self._bibitems = []
+
+        # docinfo.
+        self.docinfo = None
+        # inside literal block: no quote mangling.
+        self.literal_block = 0
+        self.literal_block_stack = []
+        self.literal = 0
+        # true when encoding in math mode
+        self.mathmode = 0
+
+    def to_latex_encoding(self,docutils_encoding):
+        """
+        Translate docutils encoding name into latex's.
+
+        Default fallback method is remove "-" and "_" chars from docutils_encoding.
+
+        """
+        tr = {  "iso-8859-1": "latin1",     # west european
+                "iso-8859-2": "latin2",     # east european
+                "iso-8859-3": "latin3",     # esperanto, maltese
+                "iso-8859-4": "latin4",     # north european,scandinavian, baltic
+                "iso-8859-5": "iso88595",   # cyrillic (ISO)
+                "iso-8859-9": "latin5",     # turkish
+                "iso-8859-15": "latin9",    # latin9, update to latin1.
+                "mac_cyrillic": "maccyr",   # cyrillic (on Mac)
+                "windows-1251": "cp1251",   # cyrillic (on Windows)
+                "koi8-r": "koi8-r",         # cyrillic (Russian)
+                "koi8-u": "koi8-u",         # cyrillic (Ukrainian)
+                "windows-1250": "cp1250",   #
+                "windows-1252": "cp1252",   #
+                "us-ascii": "ascii",        # ASCII (US)
+                # unmatched encodings
+                #"": "applemac",
+                #"": "ansinew",  # windows 3.1 ansi
+                #"": "ascii",    # ASCII encoding for the range 32--127.
+                #"": "cp437",    # dos latine us
+                #"": "cp850",    # dos latin 1
+                #"": "cp852",    # dos latin 2
+                #"": "decmulti",
+                #"": "latin10",
+                #"iso-8859-6": ""   # arabic
+                #"iso-8859-7": ""   # greek
+                #"iso-8859-8": ""   # hebrew
+                #"iso-8859-10": ""   # latin6, more complete iso-8859-4
+             }
+        if tr.has_key(docutils_encoding.lower()):
+            return tr[docutils_encoding.lower()]
+        return docutils_encoding.translate(string.maketrans("",""),"_-").lower()
+
+    def language_label(self, docutil_label):
+        return self.language.labels[docutil_label]
+
+    latex_equivalents = {
+        u'\u00A0' : '~',
+        u'\u2013' : '{--}',
+        u'\u2014' : '{---}',
+        u'\u2018' : '`',
+        u'\u2019' : '\'',
+        u'\u201A' : ',',
+        u'\u201C' : '``',
+        u'\u201D' : '\'\'',
+        u'\u201E' : ',,',
+        u'\u2020' : '{\\dag}',
+        u'\u2021' : '{\\ddag}',
+        u'\u2026' : '{\\dots}',
+        u'\u2122' : '{\\texttrademark}',
+        u'\u21d4' : '{$\\Leftrightarrow$}',
+    }
+
+    def unicode_to_latex(self,text):
+        # see LaTeX codec
+        # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124
+        # Only some special chracters are translated, for documents with many
+        # utf-8 chars one should use the LaTeX unicode package.
+        for uchar in self.latex_equivalents.keys():
+            text = text.replace(uchar,self.latex_equivalents[uchar])
+        return text
+
+    def encode(self, text):
+        """
+        Encode special characters in `text` & return.
+            # $ % & ~ _ ^ \ { }
+        Escaping with a backslash does not help with backslashes, ~ and ^.
+
+            < > are only available in math-mode or tt font. (really ?)
+            $ starts math- mode.
+        AND quotes:
+
+        """
+        if self.verbatim:
+            return text
+        # compile the regexps once. do it here so one can see them.
+        #
+        # first the braces.
+        if not self.__dict__.has_key('encode_re_braces'):
+            self.encode_re_braces = re.compile(r'([{}])')
+        text = self.encode_re_braces.sub(r'{\\\1}',text)
+        if not self.__dict__.has_key('encode_re_bslash'):
+            # find backslash: except in the form '{\{}' or '{\}}'.
+            self.encode_re_bslash = re.compile(r'(?<!{)(\\)(?![{}]})')
+        # then the backslash: except in the form from line above:
+        # either '{\{}' or '{\}}'.
+        text = self.encode_re_bslash.sub(r'{\\textbackslash}', text)
+
+        # then dollar
+        text = text.replace("$", '{\\$}')
+        if not ( self.literal_block or self.literal or self.mathmode ):
+            # the vertical bar: in mathmode |,\vert or \mid
+            #   in textmode \textbar
+            text = text.replace("|", '{\\textbar}')
+            text = text.replace("<", '{\\textless}')
+            text = text.replace(">", '{\\textgreater}')
+        # then
+        text = text.replace("&", '{\\&}')
+        # the ^:
+        # * verb|^| does not work in mbox.
+        # * mathmode has wedge. hat{~} would also work.
+        # text = text.replace("^", '{\\ensuremath{^\\wedge}}')
+        text = text.replace("^", '{\\textasciicircum}')
+        text = text.replace("%", '{\\%}')
+        text = text.replace("#", '{\\#}')
+        text = text.replace("~", '{\\textasciitilde}')
+        # Separate compound characters, e.g. "--" to "-{}-".  (The
+        # actual separation is done later; see below.)
+        separate_chars = '-'
+        if self.literal_block or self.literal:
+            # In monospace-font, we also separate ",,", "``" and "''"
+            # and some other characters which can't occur in
+            # non-literal text.
+            separate_chars += ',`\'"<>'
+            # pdflatex does not produce doublequotes for ngerman.
+            text = self.babel.double_quotes_in_tt(text)
+            if self.font_encoding == 'OT1':
+                # We're using OT1 font-encoding and have to replace
+                # underscore by underlined blank, because this has
+                # correct width.
+                text = text.replace('_', '{\\underline{ }}')
+                # And the tt-backslash doesn't work in OT1, so we use
+                # a mirrored slash.
+                text = text.replace('\\textbackslash', '\\reflectbox{/}')
+            else:
+                text = text.replace('_', '{\\_}')
+        else:
+            text = self.babel.quote_quotes(text)
+            text = text.replace("_", '{\\_}')
+        for char in separate_chars * 2:
+            # Do it twice ("* 2") becaues otherwise we would replace
+            # "---" by "-{}--".
+            text = text.replace(char + char, char + '{}' + char)
+        if self.insert_newline or self.literal_block:
+            # Insert a blank before the newline, to avoid
+            # ! LaTeX Error: There's no line here to end.
+            text = text.replace("\n", '~\\\\\n')
+        elif self.mbox_newline:
+            if self.literal_block:
+                closings = "}" * len(self.literal_block_stack)
+                openings = "".join(self.literal_block_stack)
+            else:
+                closings = ""
+                openings = ""
+            text = text.replace("\n", "%s}\\\\\n\\mbox{%s" % (closings,openings))
+        # lines starting with "[" give errors.
+        text = text.replace('[', '{[}')
+        if self.insert_none_breaking_blanks:
+            text = text.replace(' ', '~')
+        if self.latex_encoding != 'utf8':
+            text = self.unicode_to_latex(text)
+        return text
+
+    def attval(self, text,
+               whitespace=re.compile('[\n\r\t\v\f]')):
+        """Cleanse, encode, and return attribute value text."""
+        return self.encode(whitespace.sub(' ', text))
+
+    def astext(self):
+        if self.pdfinfo:
+            if self.pdfauthor:
+                self.pdfinfo.append('pdfauthor={%s}' % self.pdfauthor)
+            pdfinfo = '\\hypersetup{\n' + ',\n'.join(self.pdfinfo) + '\n}\n'
+        else:
+            pdfinfo = ''
+        title = '\\title{%s}\n' % self.title
+        return ''.join(self.head_prefix + [title]
+                        + self.head + [pdfinfo]
+                        + self.body_prefix  + self.body + self.body_suffix)
+
+    def visit_Text(self, node):
+        self.body.append(self.encode(node.astext()))
+
+    def depart_Text(self, node):
+        pass
+
+    def visit_address(self, node):
+        self.visit_docinfo_item(node, 'address')
+
+    def depart_address(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_admonition(self, node, name=''):
+        self.body.append('\\begin{center}\\begin{sffamily}\n')
+        self.body.append('\\fbox{\\parbox{\\admonitionwidth}{\n')
+        if name:
+            self.body.append('\\textbf{\\large '+ self.language.labels[name] + '}\n');
+        self.body.append('\\vspace{2mm}\n')
+
+
+    def depart_admonition(self, node=None):
+        self.body.append('}}\n') # end parbox fbox
+        self.body.append('\\end{sffamily}\n\\end{center}\n');
+
+    def visit_attention(self, node):
+        self.visit_admonition(node, 'attention')
+
+    def depart_attention(self, node):
+        self.depart_admonition()
+
+    def visit_author(self, node):
+        self.visit_docinfo_item(node, 'author')
+
+    def depart_author(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_authors(self, node):
+        # not used: visit_author is called anyway for each author.
+        if self.use_latex_docinfo:
+            self.author_stack = []
+
+    def depart_authors(self, node):
+        if self.use_latex_docinfo:
+            self.head.append('\\author{%s}\n' % \
+                ' \\and '.join(self.author_stack) )
+            self.author_stack = None
+
+    def visit_block_quote(self, node):
+        self.body.append( '\\begin{quote}\n')
+
+    def depart_block_quote(self, node):
+        self.body.append( '\\end{quote}\n')
+
+    def visit_bullet_list(self, node):
+        if self.topic_class == 'contents':
+            if not self.use_latex_toc:
+                self.body.append( '\\begin{list}{}{}\n' )
+        else:
+            self.body.append( '\\begin{itemize}\n' )
+
+    def depart_bullet_list(self, node):
+        if self.topic_class == 'contents':
+            if not self.use_latex_toc:
+                self.body.append( '\\end{list}\n' )
+        else:
+            self.body.append( '\\end{itemize}\n' )
+
+    # Imperfect superscript/subscript handling: mathmode italicizes
+    # all letters by default.
+    def visit_superscript(self, node):
+        self.body.append('$^{')
+        self.mathmode = 1
+
+    def depart_superscript(self, node):
+        self.body.append('}$')
+        self.mathmode = 0
+
+    def visit_subscript(self, node):
+        self.body.append('$_{')
+        self.mathmode = 1
+
+    def depart_subscript(self, node):
+        self.body.append('}$')
+        self.mathmode = 0
+
+    def visit_caption(self, node):
+        self.body.append( '\\caption{' )
+
+    def depart_caption(self, node):
+        self.body.append('}')
+
+    def visit_caution(self, node):
+        self.visit_admonition(node, 'caution')
+
+    def depart_caution(self, node):
+        self.depart_admonition()
+
+    def visit_title_reference(self, node):
+        self.body.append( '\\titlereference{' )
+
+    def depart_title_reference(self, node):
+        self.body.append( '}' )
+
+    def visit_citation(self, node):
+        # TODO maybe use cite bibitems
+        if self._use_latex_citations:
+            self.context.append(len(self.body))
+        else:
+            self.body.append('\\begin{figure}[b]')
+            self.body.append('\\hypertarget{%s}' % node['id'])
+
+    def depart_citation(self, node):
+        if self._use_latex_citations:
+            size = self.context.pop()
+            label = self.body[size]
+            text = ''.join(self.body[size+1:])
+            del self.body[size:]
+            self._bibitems.append([label, text])
+        else:
+            self.body.append('\\end{figure}\n')
+
+    def visit_citation_reference(self, node):
+        if self._use_latex_citations:
+            self.body.append('\\cite{')
+        else:
+            href = ''
+            if node.has_key('refid'):
+                href = node['refid']
+            elif node.has_key('refname'):
+                href = self.document.nameids[node['refname']]
+            self.body.append('[\\hyperlink{%s}{' % href)
+
+    def depart_citation_reference(self, node):
+        if self._use_latex_citations:
+            self.body.append('}')
+        else:
+            self.body.append('}]')
+
+    def visit_classifier(self, node):
+        self.body.append( '(\\textbf{' )
+
+    def depart_classifier(self, node):
+        self.body.append( '})\n' )
+
+    def visit_colspec(self, node):
+        self.active_table.visit_colspec(node)
+
+    def depart_colspec(self, node):
+        pass
+
+    def visit_comment(self, node):
+        # Escape end of line by a new comment start in comment text.
+        self.body.append('%% %s \n' % node.astext().replace('\n', '\n% '))
+        raise nodes.SkipNode
+
+    def visit_compound(self, node):
+        pass
+
+    def depart_compound(self, node):
+        pass
+
+    def visit_contact(self, node):
+        self.visit_docinfo_item(node, 'contact')
+
+    def depart_contact(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_copyright(self, node):
+        self.visit_docinfo_item(node, 'copyright')
+
+    def depart_copyright(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_danger(self, node):
+        self.visit_admonition(node, 'danger')
+
+    def depart_danger(self, node):
+        self.depart_admonition()
+
+    def visit_date(self, node):
+        self.visit_docinfo_item(node, 'date')
+
+    def depart_date(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_decoration(self, node):
+        pass
+
+    def depart_decoration(self, node):
+        pass
+
+    def visit_definition(self, node):
+        self.body.append('%[visit_definition]\n')
+
+    def depart_definition(self, node):
+        self.body.append('\n')
+        self.body.append('%[depart_definition]\n')
+
+    def visit_definition_list(self, node):
+        self.body.append( '\\begin{description}\n' )
+
+    def depart_definition_list(self, node):
+        self.body.append( '\\end{description}\n' )
+
+    def visit_definition_list_item(self, node):
+        self.body.append('%[visit_definition_list_item]\n')
+
+    def depart_definition_list_item(self, node):
+        self.body.append('%[depart_definition_list_item]\n')
+
+    def visit_description(self, node):
+        if self.use_optionlist_for_option_list:
+            self.body.append( ' ' )
+        else:
+            self.body.append( ' & ' )
+
+    def depart_description(self, node):
+        pass
+
+    def visit_docinfo(self, node):
+        self.docinfo = []
+        self.docinfo.append('%' + '_'*75 + '\n')
+        self.docinfo.append('\\begin{center}\n')
+        self.docinfo.append('\\begin{tabularx}{\\docinfowidth}{lX}\n')
+
+    def depart_docinfo(self, node):
+        self.docinfo.append('\\end{tabularx}\n')
+        self.docinfo.append('\\end{center}\n')
+        self.body = self.docinfo + self.body
+        # clear docinfo, so field names are no longer appended.
+        self.docinfo = None
+
+    def visit_docinfo_item(self, node, name):
+        if name == 'author':
+            if not self.pdfinfo == None:
+                if not self.pdfauthor:
+                    self.pdfauthor = self.attval(node.astext())
+                else:
+                    self.pdfauthor += self.author_separator + self.attval(node.astext())
+            if self.use_latex_docinfo:
+                if self.author_stack == None:
+                    self.head.append('\\author{%s}\n' % self.attval(node.astext()))
+                else:
+                    self.author_stack.append( self.attval(node.astext()) )
+                raise nodes.SkipNode
+        elif name == 'date':
+            if self.use_latex_docinfo:
+                self.head.append('\\date{%s}\n' % self.attval(node.astext()))
+                raise nodes.SkipNode
+        self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name))
+        if name == 'address':
+            self.insert_newline = 1
+            self.docinfo.append('{\\raggedright\n')
+            self.context.append(' } \\\\\n')
+        else:
+            self.context.append(' \\\\\n')
+        self.context.append(self.docinfo)
+        self.context.append(len(self.body))
+
+    def depart_docinfo_item(self, node):
+        size = self.context.pop()
+        dest = self.context.pop()
+        tail = self.context.pop()
+        tail = self.body[size:] + [tail]
+        del self.body[size:]
+        dest.extend(tail)
+        # for address we did set insert_newline
+        self.insert_newline = 0
+
+    def visit_doctest_block(self, node):
+        self.body.append( '\\begin{verbatim}' )
+        self.verbatim = 1
+
+    def depart_doctest_block(self, node):
+        self.body.append( '\\end{verbatim}\n' )
+        self.verbatim = 0
+
+    def visit_document(self, node):
+        self.body_prefix.append('\\begin{document}\n')
+        # titled document?
+        if len(node) and isinstance(node[0], nodes.title):
+            self.body_prefix.append('\\maketitle\n\n')
+            # alternative use titlepage environment.
+            # \begin{titlepage}
+        self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')
+
+    def depart_document(self, node):
+        # TODO insertion point of bibliography should none automatic.
+        if self._use_latex_citations and len(self._bibitems)>0:
+            widest_label = ""
+            for bi in self._bibitems:
+                if len(widest_label)<len(bi[0]):
+                    widest_label = bi[0]
+            self.body.append('\n\\begin{thebibliography}{%s}\n'%widest_label)
+            for bi in self._bibitems:
+                self.body.append('\\bibitem[%s]{%s}{%s}\n' % (bi[0], bi[0], bi[1]))
+            self.body.append('\\end{thebibliography}\n')
+            
+        self.body_suffix.append('\\end{document}\n')
+
+    def visit_emphasis(self, node):
+        self.body.append('\\emph{')
+        self.literal_block_stack.append('\\emph{')
+
+    def depart_emphasis(self, node):
+        self.body.append('}')
+        self.literal_block_stack.pop()
+
+    def visit_entry(self, node):
+        self.active_table.visit_entry()
+        # cell separation
+        if self.active_table.get_entry_number() == 1:
+            # if the firstrow is a multirow, this actually is the second row.
+            # this gets hairy if rowspans follow each other.
+            if self.active_table.get_rowspan(0):
+                self.body.append(' & ')
+                self.active_table.visit_entry() # increment cell count
+        else:
+            self.body.append(' & ')
+
+        # multi{row,column}
+        # IN WORK BUG TODO HACK continues here
+        # multirow in LaTeX simply will enlarge the cell over several rows
+        # (the following n if n is positive, the former if negative).
+        if node.has_key('morerows') and node.has_key('morecols'):
+            raise NotImplementedError('Cells that '
+            'span multiple rows *and* columns are not supported, sorry.')
+        if node.has_key('morerows'):
+            count = node['morerows'] + 1
+            self.active_table.set_rowspan(self.active_table.get_entry_number()-1,count)
+            self.body.append('\\multirow{%d}{%s}{' % \
+                    (count,self.active_table.get_column_width()))
+            self.context.append('}')
+            # BUG following rows must have empty cells.
+        elif node.has_key('morecols'):
+            # the vertical bar before column is missing if it is the first column.
+            # the one after always.
+            if self.active_table.get_entry_number() == 1:
+                bar1 = self.active_table.get_vertical_bar()
+            else:
+                bar1 = ''
+            count = node['morecols'] + 1
+            self.body.append('\\multicolumn{%d}{%sl%s}{' % \
+                    (count, bar1, self.active_table.get_vertical_bar()))
+            self.context.append('}')
+        else:
+            self.context.append('')
+
+        # header / not header
+        if isinstance(node.parent.parent, nodes.thead):
+            self.body.append('\\textbf{')
+            self.context.append('}')
+        else:
+            self.context.append('')
+
+    def depart_entry(self, node):
+        self.body.append(self.context.pop()) # header / not header
+        self.body.append(self.context.pop()) # multirow/column
+        # if following row is spanned from above.
+        if self.active_table.get_rowspan(self.active_table.get_entry_number()):
+           self.body.append(' & ')
+           self.active_table.visit_entry() # increment cell count
+
+    def visit_row(self, node):
+        self.active_table.visit_row()
+
+    def depart_row(self, node):
+        self.body.extend(self.active_table.depart_row())
+
+    def visit_enumerated_list(self, node):
+        # We create our own enumeration list environment.
+        # This allows to set the style and starting value
+        # and unlimited nesting.
+        self._enum_cnt += 1
+
+        enum_style = {'arabic':'arabic',
+                'loweralpha':'alph',
+                'upperalpha':'Alph',
+                'lowerroman':'roman',
+                'upperroman':'Roman' }
+        enum_suffix = ""
+        if node.has_key('suffix'):
+            enum_suffix = node['suffix']
+        enum_prefix = ""
+        if node.has_key('prefix'):
+            enum_prefix = node['prefix']
+        if self.compound_enumerators:
+            pref = ""
+            if self.section_prefix_for_enumerators and self.section_level:
+                for i in range(self.section_level):
+                    pref += '%d.' % self._section_number[i]
+                pref = pref[:-1] + self.section_enumerator_separator
+                enum_prefix += pref
+            for counter in self._enumeration_counters:
+                enum_prefix += counter + '.'
+        enum_type = "arabic"
+        if node.has_key('enumtype'):
+            enum_type = node['enumtype']
+        if enum_style.has_key(enum_type):
+            enum_type = enum_style[enum_type]
+        counter_name = "listcnt%d" % self._enum_cnt;
+        self._enumeration_counters.append("\\%s{%s}" % (enum_type,counter_name))
+        self.body.append('\\newcounter{%s}\n' % counter_name)
+        self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' % \
+            (enum_prefix,enum_type,counter_name,enum_suffix))
+        self.body.append('{\n')
+        self.body.append('\\usecounter{%s}\n' % counter_name)
+        # set start after usecounter, because it initializes to zero.
+        if node.has_key('start'):
+            self.body.append('\\addtocounter{%s}{%d}\n' \
+                    % (counter_name,node['start']-1))
+        ## set rightmargin equal to leftmargin
+        self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
+        self.body.append('}\n')
+
+    def depart_enumerated_list(self, node):
+        self.body.append('\\end{list}\n')
+        self._enumeration_counters.pop()
+
+    def visit_error(self, node):
+        self.visit_admonition(node, 'error')
+
+    def depart_error(self, node):
+        self.depart_admonition()
+
+    def visit_field(self, node):
+        # real output is done in siblings: _argument, _body, _name
+        pass
+
+    def depart_field(self, node):
+        self.body.append('\n')
+        ##self.body.append('%[depart_field]\n')
+
+    def visit_field_argument(self, node):
+        self.body.append('%[visit_field_argument]\n')
+
+    def depart_field_argument(self, node):
+        self.body.append('%[depart_field_argument]\n')
+
+    def visit_field_body(self, node):
+        # BUG by attach as text we loose references.
+        if self.docinfo:
+            self.docinfo.append('%s \\\\\n' % self.encode(node.astext()))
+            raise nodes.SkipNode
+        # BUG: what happens if not docinfo
+
+    def depart_field_body(self, node):
+        self.body.append( '\n' )
+
+    def visit_field_list(self, node):
+        if not self.docinfo:
+            self.body.append('\\begin{quote}\n')
+            self.body.append('\\begin{description}\n')
+
+    def depart_field_list(self, node):
+        if not self.docinfo:
+            self.body.append('\\end{description}\n')
+            self.body.append('\\end{quote}\n')
+
+    def visit_field_name(self, node):
+        # BUG this duplicates docinfo_item
+        if self.docinfo:
+            self.docinfo.append('\\textbf{%s}: &\n\t' % self.encode(node.astext()))
+            raise nodes.SkipNode
+        else:
+            self.body.append('\\item [')
+
+    def depart_field_name(self, node):
+        if not self.docinfo:
+            self.body.append(':]')
+
+    def visit_figure(self, node):
+        self.body.append( '\\begin{figure}[htbp]\\begin{center}\n' )
+
+    def depart_figure(self, node):
+        self.body.append( '\\end{center}\\end{figure}\n' )
+
+    def visit_footer(self, node):
+        self.context.append(len(self.body))
+
+    def depart_footer(self, node):
+        start = self.context.pop()
+        footer = (['\n\\begin{center}\small\n']
+                  + self.body[start:] + ['\n\\end{center}\n'])
+        self.body_suffix[:0] = footer
+        del self.body[start:]
+
+    def visit_footnote(self, node):
+        if self.use_latex_footnotes:
+            num,text = node.astext().split(None,1)
+            num = self.encode(num.strip())
+            self.body.append('\\footnotetext['+num+']')
+            self.body.append('{')
+        else:
+            self.body.append('\\begin{figure}[b]')
+            self.body.append('\\hypertarget{%s}' % node['id'])
+
+    def depart_footnote(self, node):
+        if self.use_latex_footnotes:
+            self.body.append('}\n')
+        else:
+            self.body.append('\\end{figure}\n')
+
+    def visit_footnote_reference(self, node):
+        if self.use_latex_footnotes:
+            self.body.append("\\footnotemark["+self.encode(node.astext())+"]")
+            raise nodes.SkipNode
+        href = ''
+        if node.has_key('refid'):
+            href = node['refid']
+        elif node.has_key('refname'):
+            href = self.document.nameids[node['refname']]
+        format = self.settings.footnote_references
+        if format == 'brackets':
+            suffix = '['
+            self.context.append(']')
+        elif format == 'superscript':
+            suffix = '\\raisebox{.5em}[0em]{\\scriptsize'
+            self.context.append('}')
+        else:                           # shouldn't happen
+            raise AssertionError('Illegal footnote reference format.')
+        self.body.append('%s\\hyperlink{%s}{' % (suffix,href))
+
+    def depart_footnote_reference(self, node):
+        if self.use_latex_footnotes:
+            return
+        self.body.append('}%s' % self.context.pop())
+
+    # footnote/citation label
+    def label_delim(self, node, bracket, superscript):
+        if isinstance(node.parent, nodes.footnote):
+            if self.use_latex_footnotes:
+                raise nodes.SkipNode
+            if self.settings.footnote_references == 'brackets':
+                self.body.append(bracket)
+            else:
+                self.body.append(superscript)
+        else:
+            assert isinstance(node.parent, nodes.citation)
+            if not self._use_latex_citations:
+                self.body.append(bracket)
+
+    def visit_label(self, node):
+        self.label_delim(node, '[', '$^{')
+
+    def depart_label(self, node):
+        self.label_delim(node, ']', '}$')
+
+    # elements generated by the framework e.g. section numbers.
+    def visit_generated(self, node):
+        pass
+
+    def depart_generated(self, node):
+        pass
+
+    def visit_header(self, node):
+        self.context.append(len(self.body))
+
+    def depart_header(self, node):
+        start = self.context.pop()
+        self.body_prefix.append('\n\\verb|begin_header|\n')
+        self.body_prefix.extend(self.body[start:])
+        self.body_prefix.append('\n\\verb|end_header|\n')
+        del self.body[start:]
+
+    def visit_hint(self, node):
+        self.visit_admonition(node, 'hint')
+
+    def depart_hint(self, node):
+        self.depart_admonition()
+
+    def visit_image(self, node):
+        attrs = node.attributes
+        # Add image URI to dependency list, assuming that it's
+        # referring to a local file.
+        self.settings.record_dependencies.add(attrs['uri'])
+        pre = []                        # in reverse order
+        post = ['\\includegraphics{%s}' % attrs['uri']]
+        inline = isinstance(node.parent, nodes.TextElement)
+        if attrs.has_key('scale'):
+            # Could also be done with ``scale`` option to
+            # ``\includegraphics``; doing it this way for consistency.
+            pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
+            post.append('}')
+        if attrs.has_key('align'):
+            align_prepost = {
+                # By default latex aligns the top of an image.
+                (1, 'top'): ('', ''),
+                (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
+                (1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
+                (0, 'center'): ('{\\hfill', '\\hfill}'),
+                # These 2 don't exactly do the right thing.  The image should
+                # be floated alongside the paragraph.  See
+                # http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
+                (0, 'left'): ('{', '\\hfill}'),
+                (0, 'right'): ('{\\hfill', '}'),}
+            try:
+                pre.append(align_prepost[inline, attrs['align']][0])
+                post.append(align_prepost[inline, attrs['align']][1])
+            except KeyError:
+                pass                    # XXX complain here?
+        if not inline:
+            pre.append('\n')
+            post.append('\n')
+        pre.reverse()
+        self.body.extend(pre + post)
+
+    def depart_image(self, node):
+        pass
+
+    def visit_important(self, node):
+        self.visit_admonition(node, 'important')
+
+    def depart_important(self, node):
+        self.depart_admonition()
+
+    def visit_interpreted(self, node):
+        # @@@ Incomplete, pending a proper implementation on the
+        # Parser/Reader end.
+        self.visit_literal(node)
+
+    def depart_interpreted(self, node):
+        self.depart_literal(node)
+
+    def visit_legend(self, node):
+        self.body.append('{\\small ')
+
+    def depart_legend(self, node):
+        self.body.append('}')
+
+    def visit_line(self, node):
+        self.body.append('\item[] ')
+
+    def depart_line(self, node):
+        self.body.append('\n')
+
+    def visit_line_block(self, node):
+        if isinstance(node.parent, nodes.line_block):
+            self.body.append('\\item[] \n'
+                             '\\begin{lineblock}{\\lineblockindentation}\n')
+        else:
+            self.body.append('\n\\begin{lineblock}{0em}\n')
+
+    def depart_line_block(self, node):
+        self.body.append('\\end{lineblock}\n')
+
+    def visit_list_item(self, node):
+        # Append "{}" in case the next character is "[", which would break
+        # LaTeX's list environment (no numbering and the "[" is not printed).
+        self.body.append('\\item {} ')
+
+    def depart_list_item(self, node):
+        self.body.append('\n')
+
+    def visit_literal(self, node):
+        self.literal = 1
+        self.body.append('\\texttt{')
+
+    def depart_literal(self, node):
+        self.body.append('}')
+        self.literal = 0
+
+    def visit_literal_block(self, node):
+        """
+        Render a literal-block.
+
+        Literal blocks are used for "::"-prefixed literal-indented
+        blocks of text, where the inline markup is not recognized,
+        but are also the product of the parsed-literal directive,
+        where the markup is respected.
+        """
+        # In both cases, we want to use a typewriter/monospaced typeface.
+        # For "real" literal-blocks, we can use \verbatim, while for all
+        # the others we must use \mbox.
+        #
+        # We can distinguish between the two kinds by the number of
+        # siblings the compose this node: if it is composed by a
+        # single element, it's surely is either a real one, otherwise
+        # it's a parsed-literal that does not contain any markup.
+        #
+        if (self.settings.use_verbatim_when_possible and (len(node) == 1)
+              # in case of a parsed-literal containing just a "**bold**" word:
+              and isinstance(node[0], nodes.Text)):
+            self.verbatim = 1
+            self.body.append('\\begin{quote}\\begin{verbatim}\n')
+        else:
+            self.literal_block = 1
+            self.insert_none_breaking_blanks = 1
+            if self.active_table.is_open():
+                self.body.append('\n{\\ttfamily \\raggedright \\noindent\n')
+            else:
+                # no quote inside tables, to avoid vertical sppace between
+                # table border and literal block.
+                # BUG: fails if normal text preceeds the literal block.
+                self.body.append('\\begin{quote}')
+                self.body.append('{\\ttfamily \\raggedright \\noindent\n')
+            # * obey..: is from julien and never worked for me (grubert).
+            #   self.body.append('{\\obeylines\\obeyspaces\\ttfamily\n')
+
+    def depart_literal_block(self, node):
+        if self.verbatim:
+            self.body.append('\n\\end{verbatim}\\end{quote}\n')
+            self.verbatim = 0
+        else:
+            if self.active_table.is_open():
+                self.body.append('\n}\n')
+            else:
+                self.body.append('\n')
+                self.body.append('}\\end{quote}\n')
+            self.insert_none_breaking_blanks = 0
+            self.literal_block = 0
+            # obey end: self.body.append('}\n')
+
+    def visit_meta(self, node):
+        self.body.append('[visit_meta]\n')
+        # BUG maybe set keywords for pdf
+        ##self.head.append(self.starttag(node, 'meta', **node.attributes))
+
+    def depart_meta(self, node):
+        self.body.append('[depart_meta]\n')
+
+    def visit_note(self, node):
+        self.visit_admonition(node, 'note')
+
+    def depart_note(self, node):
+        self.depart_admonition()
+
+    def visit_option(self, node):
+        if self.context[-1]:
+            # this is not the first option
+            self.body.append(', ')
+
+    def depart_option(self, node):
+        # flag tha the first option is done.
+        self.context[-1] += 1
+
+    def visit_option_argument(self, node):
+        """The delimiter betweeen an option and its argument."""
+        self.body.append(node.get('delimiter', ' '))
+
+    def depart_option_argument(self, node):
+        pass
+
+    def visit_option_group(self, node):
+        if self.use_optionlist_for_option_list:
+            self.body.append('\\item [')
+        else:
+            if len(node.astext()) > 14:
+                self.body.append('\\multicolumn{2}{l}{')
+                self.context.append('} \\\\\n  ')
+            else:
+                self.context.append('')
+            self.body.append('\\texttt{')
+        # flag for first option
+        self.context.append(0)
+
+    def depart_option_group(self, node):
+        self.context.pop() # the flag
+        if self.use_optionlist_for_option_list:
+            self.body.append('] ')
+        else:
+            self.body.append('}')
+            self.body.append(self.context.pop())
+
+    def visit_option_list(self, node):
+        self.body.append('% [option list]\n')
+        if self.use_optionlist_for_option_list:
+            self.body.append('\\begin{optionlist}{3cm}\n')
+        else:
+            self.body.append('\\begin{center}\n')
+            # BUG: use admwidth or make it relative to textwidth ?
+            self.body.append('\\begin{tabularx}{.9\\linewidth}{lX}\n')
+
+    def depart_option_list(self, node):
+        if self.use_optionlist_for_option_list:
+            self.body.append('\\end{optionlist}\n')
+        else:
+            self.body.append('\\end{tabularx}\n')
+            self.body.append('\\end{center}\n')
+
+    def visit_option_list_item(self, node):
+        pass
+
+    def depart_option_list_item(self, node):
+        if not self.use_optionlist_for_option_list:
+            self.body.append('\\\\\n')
+
+    def visit_option_string(self, node):
+        ##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
+        pass
+
+    def depart_option_string(self, node):
+        ##self.body.append('</span>')
+        pass
+
+    def visit_organization(self, node):
+        self.visit_docinfo_item(node, 'organization')
+
+    def depart_organization(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_paragraph(self, node):
+        index = node.parent.index(node)
+        if not (self.topic_class == 'contents' or
+                (isinstance(node.parent, nodes.compound) and
+                 index > 0 and
+                 not isinstance(node.parent[index - 1], nodes.paragraph) and
+                 not isinstance(node.parent[index - 1], nodes.compound))):
+            self.body.append('\n')
+
+    def depart_paragraph(self, node):
+        self.body.append('\n')
+
+    def visit_problematic(self, node):
+        self.body.append('{\\color{red}\\bfseries{}')
+
+    def depart_problematic(self, node):
+        self.body.append('}')
+
+    def visit_raw(self, node):
+        if 'latex' in node.get('format', '').split():
+            self.body.append(node.astext())
+        raise nodes.SkipNode
+
+    def visit_reference(self, node):
+        # BUG: hash_char "#" is trouble some in LaTeX.
+        # mbox and other environment do not like the '#'.
+        hash_char = '\\#'
+        if node.has_key('refuri'):
+            href = node['refuri'].replace('#',hash_char)
+        elif node.has_key('refid'):
+            href = hash_char + node['refid']
+        elif node.has_key('refname'):
+            href = hash_char + self.document.nameids[node['refname']]
+        else:
+            raise AssertionError('Unknown reference.')
+        self.body.append('\\href{%s}{' % href)
+
+    def depart_reference(self, node):
+        self.body.append('}')
+
+    def visit_revision(self, node):
+        self.visit_docinfo_item(node, 'revision')
+
+    def depart_revision(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_section(self, node):
+        self.section_level += 1
+        # Initialize counter for potential subsections:
+        self._section_number.append(0)
+        # Counter for this section's level (initialized by parent section):
+        self._section_number[self.section_level - 1] += 1
+
+    def depart_section(self, node):
+        # Remove counter for potential subsections:
+        self._section_number.pop()
+        self.section_level -= 1
+
+    def visit_sidebar(self, node):
+        # BUG:  this is just a hack to make sidebars render something
+        self.body.append('\n\\setlength{\\locallinewidth}{0.9\\admonitionwidth}\n')
+        self.body.append('\\begin{center}\\begin{sffamily}\n')
+        self.body.append('\\fbox{\\colorbox[gray]{0.80}{\\parbox{\\admonitionwidth}{\n')
+
+    def depart_sidebar(self, node):
+        self.body.append('}}}\n') # end parbox colorbox fbox
+        self.body.append('\\end{sffamily}\n\\end{center}\n');
+        self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')
+
+
+    attribution_formats = {'dash': ('---', ''),
+                           'parentheses': ('(', ')'),
+                           'parens': ('(', ')'),
+                           'none': ('', '')}
+
+    def visit_attribution(self, node):
+        prefix, suffix = self.attribution_formats[self.settings.attribution]
+        self.body.append('\n\\begin{flushright}\n')
+        self.body.append(prefix)
+        self.context.append(suffix)
+
+    def depart_attribution(self, node):
+        self.body.append(self.context.pop() + '\n')
+        self.body.append('\\end{flushright}\n')
+
+    def visit_status(self, node):
+        self.visit_docinfo_item(node, 'status')
+
+    def depart_status(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_strong(self, node):
+        self.body.append('\\textbf{')
+        self.literal_block_stack.append('\\textbf{')
+
+    def depart_strong(self, node):
+        self.body.append('}')
+        self.literal_block_stack.pop()
+
+    def visit_substitution_definition(self, node):
+        raise nodes.SkipNode
+
+    def visit_substitution_reference(self, node):
+        self.unimplemented_visit(node)
+
+    def visit_subtitle(self, node):
+        if isinstance(node.parent, nodes.sidebar):
+            self.body.append('~\\\\\n\\textbf{')
+            self.context.append('}\n\\smallskip\n')
+        else:
+            self.title = self.title + \
+                '\\\\\n\\large{%s}\n' % self.encode(node.astext())
+            raise nodes.SkipNode
+
+    def depart_subtitle(self, node):
+        if isinstance(node.parent, nodes.sidebar):
+            self.body.append(self.context.pop())
+
+    def visit_system_message(self, node):
+        if node['level'] < self.document.reporter['writer'].report_level:
+            raise nodes.SkipNode
+
+    def depart_system_message(self, node):
+        self.body.append('\n')
+
+    def visit_table(self, node):
+        if self.active_table.is_open():
+            print 'nested tables are not supported'
+            raise AssertionError
+        self.active_table.open()
+        self.body.append('\n' + self.active_table.get_opening())
+
+    def depart_table(self, node):
+        self.body.append(self.active_table.get_closing() + '\n')
+        self.active_table.close()
+
+    def visit_target(self, node):
+        # BUG: why not (refuri or refid or refname) means not footnote ?
+        if not (node.has_key('refuri') or node.has_key('refid')
+                or node.has_key('refname')):
+            self.body.append('\\hypertarget{%s}{' % node['id'])
+            self.context.append('}')
+        else:
+            self.context.append('')
+
+    def depart_target(self, node):
+        self.body.append(self.context.pop())
+
+    def visit_tbody(self, node):
+        # BUG write preamble if not yet done (colspecs not [])
+        # for tables without heads.
+        if not self.active_table.get('preamble written'):
+            self.visit_thead(None)
+            # self.depart_thead(None)
+
+    def depart_tbody(self, node):
+        pass
+
+    def visit_term(self, node):
+        self.body.append('\\item[')
+
+    def depart_term(self, node):
+        # definition list term.
+        self.body.append('] ')
+
+    def visit_tgroup(self, node):
+        #self.body.append(self.starttag(node, 'colgroup'))
+        #self.context.append('</colgroup>\n')
+        pass
+
+    def depart_tgroup(self, node):
+        pass
+
+    def visit_thead(self, node):
+        self.body.append('{%s}\n' % self.active_table.get_colspecs())
+        if self.active_table.caption:
+            self.body.append('\\caption{%s}\\\\\n' % self.active_table.caption)
+        self.active_table.set('preamble written',1)
+        # TODO longtable supports firsthead and lastfoot too.
+        self.body.extend(self.active_table.visit_thead())
+
+    def depart_thead(self, node):
+        # the table header written should be on every page
+        # => \endhead
+        self.body.extend(self.active_table.depart_thead())
+        # and the firsthead => \endfirsthead
+        # BUG i want a "continued from previous page" on every not
+        # firsthead, but then we need the header twice.
+        #
+        # there is a \endfoot and \endlastfoot too.
+        # but we need the number of columns to
+        # self.body.append('\\multicolumn{%d}{c}{"..."}\n' % number_of_columns)
+        # self.body.append('\\hline\n\\endfoot\n')
+        # self.body.append('\\hline\n')
+        # self.body.append('\\endlastfoot\n')
+
+    def visit_tip(self, node):
+        self.visit_admonition(node, 'tip')
+
+    def depart_tip(self, node):
+        self.depart_admonition()
+
+    def bookmark(self, node):
+        """Append latex href and pdfbookmarks for titles.
+        """
+        if node.parent.hasattr('id'):
+            self.body.append('\\hypertarget{%s}{}\n' % node.parent['id'])
+            if not self.use_latex_toc:
+                # BUG level depends on style. pdflatex allows level 0 to 3
+                # ToC would be the only on level 0 so i choose to decrement the rest.
+                # "Table of contents" bookmark to see the ToC. To avoid this
+                # we set all zeroes to one.
+                l = self.section_level
+                if l>0:
+                    l = l-1
+                # pdftex does not like "_" subscripts in titles
+                text = self.encode(node.astext())
+                self.body.append('\\pdfbookmark[%d]{%s}{%s}\n' % \
+                        (l,text,node.parent['id']))
+
+    def visit_title(self, node):
+        """Only 3 section levels are supported by LaTeX article (AFAIR)."""
+
+        if isinstance(node.parent, nodes.topic):
+            # section titles before the table of contents.
+            self.bookmark(node)
+            # BUG: latex chokes on center environment with "perhaps a missing item".
+            # so we use hfill.
+            self.body.append('\\subsubsection*{~\\hfill ')
+            # the closing brace for subsection.
+            self.context.append('\\hfill ~}\n')
+        # TODO: for admonition titles before the first section
+        # either specify every possible node or ... ?
+        elif isinstance(node.parent, nodes.sidebar) \
+        or isinstance(node.parent, nodes.admonition):
+            self.body.append('\\textbf{\\large ')
+            self.context.append('}\n\\smallskip\n')
+        elif isinstance(node.parent, nodes.table):
+            # caption must be written after column spec
+            self.active_table.caption = self.encode(node.astext())
+            raise nodes.SkipNode
+        elif self.section_level == 0:
+            # document title
+            self.title = self.encode(node.astext())
+            if not self.pdfinfo == None:
+                self.pdfinfo.append( 'pdftitle={%s}' % self.encode(node.astext()) )
+            raise nodes.SkipNode
+        else:
+            self.body.append('\n\n')
+            self.body.append('%' + '_' * 75)
+            self.body.append('\n\n')
+            self.bookmark(node)
+
+            if self.use_latex_toc:
+                section_star = ""
+            else:
+                section_star = "*"
+
+            section_name = self.d_class.section(self.section_level)
+            self.body.append('\\%s%s{' % (section_name, section_star))
+
+            self.context.append('}\n')
+
+    def depart_title(self, node):
+        self.body.append(self.context.pop())
+
+    def visit_topic(self, node):
+        self.topic_class = node.get('class')
+        if self.use_latex_toc:
+            self.body.append('\\tableofcontents\n\n\\bigskip\n')
+            self.topic_class = ''
+            raise nodes.SkipNode
+
+    def visit_inline(self, node): # titlereference
+        self.body.append( '\\docutilsrole%s{' % node.get('class'))
+
+    def depart_inline(self, node):
+        self.body.append( '}' )
+
+    def depart_topic(self, node):
+        self.topic_class = ''
+        self.body.append('\n')
+
+    def visit_rubric(self, node):
+        self.body.append('\\rubric{')
+        self.context.append('}\n')
+
+    def depart_rubric(self, node):
+        self.body.append(self.context.pop())
+
+    def visit_transition(self, node):
+        self.body.append('\n\n')
+        self.body.append('%' + '_' * 75)
+        self.body.append('\n\\hspace*{\\fill}\\hrulefill\\hspace*{\\fill}')
+        self.body.append('\n\n')
+
+    def depart_transition(self, node):
+        pass
+
+    def visit_version(self, node):
+        self.visit_docinfo_item(node, 'version')
+
+    def depart_version(self, node):
+        self.depart_docinfo_item(node)
+
+    def visit_warning(self, node):
+        self.visit_admonition(node, 'warning')
+
+    def depart_warning(self, node):
+        self.depart_admonition()
+
+    def unimplemented_visit(self, node):
+        raise NotImplementedError('visiting unimplemented node type: %s'
+                                  % node.__class__.__name__)
+
+#    def unknown_visit(self, node):
+#    def default_visit(self, node):
+
+# vim: set ts=4 et ai :

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/pep_html.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/pep_html.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/pep_html.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,86 @@
+# Author: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+PEP HTML Writer.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+import random
+import sys
+import docutils
+from docutils import frontend, nodes, utils
+from docutils.writers import html4css1
+
+
+class Writer(html4css1.Writer):
+
+    settings_spec = html4css1.Writer.settings_spec + (
+        'PEP/HTML-Specific Options',
+        """The HTML --footnote-references option's default is set to """
+        '"brackets".',
+        (('Specify a template file.  Default is "pep-html-template".',
+          ['--template'],
+          {'default': 'pep-html-template', 'metavar': '<file>'}),
+         ('Python\'s home URL.  Default is ".." (parent directory).',
+          ['--python-home'],
+          {'default': '..', 'metavar': '<URL>'}),
+         ('Home URL prefix for PEPs.  Default is "." (current directory).',
+          ['--pep-home'],
+          {'default': '.', 'metavar': '<URL>'}),))
+
+    settings_default_overrides = {'footnote_references': 'brackets'}
+
+    relative_path_settings = (html4css1.Writer.relative_path_settings
+                              + ('template',))
+
+    config_section = 'pep_html writer'
+    config_section_dependencies = ('writers', 'html4css1 writer')
+
+    def __init__(self):
+        html4css1.Writer.__init__(self)
+        self.translator_class = HTMLTranslator
+
+    def translate(self):
+        html4css1.Writer.translate(self)
+        settings = self.document.settings
+        template = open(settings.template).read()
+        # Substitutions dict for template:
+        subs = {}
+        subs['encoding'] = settings.output_encoding
+        subs['version'] = docutils.__version__
+        subs['stylesheet'] = ''.join(self.stylesheet)
+        pyhome = settings.python_home
+        subs['pyhome'] = pyhome
+        subs['pephome'] = settings.pep_home
+        if pyhome == '..':
+            subs['pepindex'] = '.'
+        else:
+            subs['pepindex'] = pyhome + '/peps/'
+        index = self.document.first_child_matching_class(nodes.field_list)
+        header = self.document[index]
+        pepnum = header[0][1].astext()
+        subs['pep'] = pepnum
+        subs['banner'] = random.randrange(64)
+        try:
+            subs['pepnum'] = '%04i' % int(pepnum)
+        except ValueError:
+            subs['pepnum'] = pepnum
+        subs['title'] = header[1][1].astext()
+        subs['body'] = ''.join(
+            self.body_pre_docinfo + self.docinfo + self.body)
+        subs['body_suffix'] = ''.join(self.body_suffix)
+        self.output = template % subs
+
+
+class HTMLTranslator(html4css1.HTMLTranslator):
+
+    def depart_field_list(self, node):
+        html4css1.HTMLTranslator.depart_field_list(self, node)
+        if node.get('class') == 'rfc2822':
+             self.body.append('<hr />\n')

Added: Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/pseudoxml.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/pseudoxml.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/docutils/writers/pseudoxml.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -0,0 +1,33 @@
+# Authors: David Goodger
+# Contact: goodger at users.sourceforge.net
+# Revision: $Revision: 1.2.10.6 $
+# Date: $Date: 2005/01/07 13:26:06 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple internal document tree Writer, writes indented pseudo-XML.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+from docutils import writers
+
+
+class Writer(writers.Writer):
+
+    supported = ('pprint', 'pformat', 'pseudoxml')
+    """Formats this writer supports."""
+
+    config_section = 'pseudoxml writer'
+    config_section_dependencies = ('writers',)
+
+    output = None
+    """Final translated form of `document`."""
+
+    def translate(self):
+        self.output = self.document.pformat()
+
+    def supports(self, format):
+        """This writer supports all format-specific elements."""
+        return 1

Deleted: Zope/branches/ajung-docutils-integration/lib/python/sitecustomize.py
===================================================================
--- Zope/branches/ajung-docutils-integration/lib/python/sitecustomize.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/lib/python/sitecustomize.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -1,4 +0,0 @@
-import sys, os
-dirname = os.path.dirname(__file__)
-sys.path.append(os.path.join(dirname, 'third_party', 'docutils'))
-sys.path.append(os.path.join(dirname, 'third_party', 'docutils', 'extras'))

Modified: Zope/branches/ajung-docutils-integration/setup.py
===================================================================
--- Zope/branches/ajung-docutils-integration/setup.py	2005-01-15 15:38:37 UTC (rev 28840)
+++ Zope/branches/ajung-docutils-integration/setup.py	2005-01-15 15:47:16 UTC (rev 28841)
@@ -281,14 +281,14 @@
                   sources=['DocumentTemplate/cDocumentTemplate.c'])]
     )
 
-# third_party.docutils.docutils
+# docutils
 setup(
-    name='third_party.docutils.docutils',
+    name='docutils',
     author='David Goodger and contributors',
-    packages=['third_party.docutils.docutils', 'third_party.docutils.docutils.languages', 'third_party.docutils.docutils.parsers',
-              'third_party.docutils.docutils.parsers.rst', 'third_party.docutils.docutils.parsers.rst.directives',
-              'third_party.docutils.docutils.parsers.rst.languages', 'third_party.docutils.docutils.readers',
-              'third_party.docutils.docutils.transforms', 'third_party.docutils.docutils.writers'],
+    packages=['docutils', 'docutils.languages', 'docutils.parsers',
+              'docutils.parsers.rst', 'docutils.parsers.rst.directives',
+              'docutils.parsers.rst.languages', 'docutils.readers',
+              'docutils.transforms', 'docutils.writers'],
     )
 
 # ExtensionClass
@@ -1080,14 +1080,7 @@
                 ['ZServer/medusa/test', ['ZServer/medusa/test/*.txt']]],
     )
 
-setup(
-    name='Site Customization',
-    author=AUTHOR,
 
-    data_files=[['', ['sitecustomize.py']],
-               ]
-    )
-
 # Call distutils setup with all lib/python packages and modules, and
 # flush setup_info.  Wondering why we run py_modules separately?  So am I.
 # Distutils won't let us specify packages and py_modules in the same call.



More information about the Zope-Checkins mailing list