[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
+ ``&``.
+
+* ``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 —,
+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, "—" in input text is
+7 discrete characters; no interpretation happens. When writing HTML,
+the "&" is converted to "&", so in the raw output you'd see
+"&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 "—" you'd have to write
+"&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 " ".
+ - "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 "@" 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 `` `` 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 [¡-ÿ]
+ escape {unicode}|\\[ -~¡-ÿ]
+ 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. ``☮``). 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. ``☮``). 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 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('@', ' at ')
+ 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 †
+ u'\u2021', # double dagger ‡
+ u'\u00A7', # section mark §
+ u'\u00B6', # paragraph mark (pilcrow) ¶
+ # (parallels ['||'] in CMoS)
+ '#', # number sign
+ # The entries below were chosen arbitrarily.
+ u'\u2660', # spade suit ♠
+ u'\u2665', # heart suit ♥
+ u'\u2666', # diamond suit ♦
+ u'\u2663', # club suit ♣
+ ]
+
+ 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("&", "&")
+ text = text.replace("<", "<")
+ text = text.replace('"', """)
+ text = text.replace(">", ">")
+ text = text.replace("@", "@") # may thwart some address harvesters
+ # Replace the non-breaking space character with the HTML entity:
+ text = text.replace(u'\u00a0', " ")
+ 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': ('—', ''),
+ '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(' ')
+ 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> </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(' ' * (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> </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