#
# epydoc.py: epydoc HTML output generator
# Edward Loper
#
# Created [01/30/01 05:18 PM]
# $Id: html.py,v 1.82 2004/03/19 19:03:18 edloper Exp $
#

"""
Documentation to HTML converter.  This module defines a single class,
L{HTMLFormatter}, which translates the API documentation encoded in a
L{DocMap} into a set of HTML files.

@todo: either use StringIO more (if it's significantly faster), or
       don't use it at all.
@todo: finish updating docstrings.

@newfield question: Question
@question: Should functions with no docstrings be listed in the
    details section?  Currently, they are not, with the reasoning
    being that there's nothing else to say about them.  But parameter
    defaults are listed in details, and not in summary.
    C.f. properties, where properties without docstrings are always
    listed in details (since we want to list their accessor methods).

@var HEADER: The header for standard documentation HTML pages.
@var FOOTER: The footer for standard documentation HTML pages.
@var REDIRECT_INDEX: The contents of redirect index page.  This page
    is only used if the supplied top page is external.
@var SPECIAL_METHODS: A dictionary providing names for the special
    methods that a class can define.
"""
__docformat__ = 'epytext en'

##################################################
## Implementation Notes
##################################################

# Index:
#     1. Constants
#     2. Imports
#     3. HTML Formatter
#        - Constructor
#        - Output file count
#        - Write
#        - HTML page generation
#        - Navigation bar
#        - Trees
#           - Base class trees
#           - Class hierarchy trees
#           - Module hierarchy trees
#        - Tables
#           - Class tables
#           - Function tables
#           - Variable tables
#           - Property tables
#        - Indices
#           - Term index generation
#           - Identifier index generation
#        - Table of contents (frames)
#        - Docstring->HTML Conversion
#        - Helper functions
#     4. Helper functions & classes
#

# I chose to implement the formatter as a class (rather than as a
# group of functions) because it lets me use instance variables to
# remember all the configuration variables.  Passing these around
# manually would be a pain.  Also, I don't have to explicitly pass
# around the docmap, which is used almost everywhere.

##################################################
## Constants
##################################################

# Expects: name
HEADER = '''<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title>%s</title>
  <link rel="stylesheet" href="epydoc.css" type="text/css"></link>
</head>
<body bgcolor="white" text="black" link="blue" vlink="#204080"
      alink="#204080">
'''
# Expects: (version, date)
FOOTER = '''
<table border="0" cellpadding="0" cellspacing="0" width="100%%">
  <tr>
    <td align="left"><font size="-2">Generated by Epydoc %s on %s</font></td>
    <td align="right"><a href="http://epydoc.sourceforge.net"
                      ><font size="-2">http://epydoc.sf.net</font></a></td>
  </tr>
</table>
</body>
</html>
'''
# Expects: (name, mainFrame_src)
FRAMES_INDEX = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
<html>
<head>
  <title> %s </title>
</head>
<frameset cols="20%%,80%%">
  <frameset rows="30%%,70%%">
    <frame src="toc.html" name="moduleListFrame">
    <frame src="toc-everything.html" name="moduleFrame">
  </frameset>
  <frame src="%s" name="mainFrame">
</frameset>
</html>
'''
# Expects (url, url, name)
REDIRECT_INDEX = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
<html>
<head>
  <title> Redirect </title>
  <meta http-equiv="refresh" content="1;url=%s">
  <link rel="stylesheet" href="epydoc.css" type="text/css"></link>
</head>
<body>
  Redirecting to the API documentation for <a href="%s">%s</a>...
</body>
</html>
'''

# Names for the __special__ methods.
SPECIAL_METHODS ={
    '__init__': 'Constructor',
    '__del__': 'Destructor',
    '__add__': 'Addition operator',
    '__sub__': 'Subtraction operator',
    '__and__': 'And operator',
    '__or__': 'Or operator',
    '__repr__': 'Representation operator',
    '__call__': 'Call operator',
    '__getattr__': 'Qualification operator',
    '__getitem__': 'Indexing operator',
    '__setitem__': 'Index assignment operator',
    '__delitem__': 'Index deletion operator',
    '__delslice__': 'Slice deletion operator',
    '__setslice__': 'Slice assignment operator',
    '__getslice__': 'Slicling operator',
    '__len__': 'Length operator',
    '__cmp__': 'Comparison operator',
    '__eq__': 'Equality operator',
    '__in__': 'Containership operator',
    '__gt__': 'Greater-than operator',
    '__lt__': 'Less-than operator',
    '__ge__': 'Greater-than-or-equals operator',
    '__le__': 'Less-than-or-equals operator',
    '__radd__': 'Right-side addition operator',
    '__hash__': 'Hashing function',
    '__contains__': 'In operator',
    '__nonzero__': 'Boolean test operator',
    '__str__': 'Informal representation operator',
    }

##################################################
## Imports
##################################################

# system imports
import re, sys, os.path, time, pprint, types
import xml.dom.minidom

try: from cStringIO import StringIO
except: from StringIO import StringIO

# epydoc imports
import epydoc
import epydoc.markup as markup
from epydoc.uid import UID, Link, findUID, make_uid
from epydoc.imports import import_module
from epydoc.objdoc import DocMap, ModuleDoc, FuncDoc
from epydoc.objdoc import ClassDoc, Var, Raise, ObjDoc
from epydoc.css import STYLESHEETS
from epydoc.help import HTML_HELP
from epydoc.colorize import colorize_re

##################################################
## Documentation -> HTML Conversion
##################################################

class HTMLFormatter:
    """
    Documentation to HTML converter.  The API documentation produced
    C{HTMLFormatter} consists of a set of HTML files.  Two
    subdirectories are created for the public and private
    documentation.  Within each subdirectories, every class and module
    is documented in its own file.  An index file, a trees file, a
    help file, and a frames-based table of contents are also created.
    In particular, C{HTMLFormatter} generates the following files:

      - X{index.html}: The standard entry point for the documentation.
        Normally, index.html is a frame index file, which defines
        three frames: two frames on the left side of the browser
        contain a table of contents, and the main frame on the right
        side of the window contains documentation pages.  But if the
        --no-frames option is used, then index.html will redirect the
        user to the project's top page.
      - X{I{module}-module.html}: The API documentation for a module.
        I{module} is the complete dotted name of the module, such as
        sys or epydoc.epytext.
      - X{I{class}-class.html}: The API documentation for a class, exception,
        or type.  I{class} is the complete dotted name of the class,
        such as epydoc.epytext.Token or array.ArrayType.
      - X{trees.html}: The module and class hierarchies.
      - X{indices.html} The term and identifier indices.  
      - X{help.html}: The help page for the project.  This page
        explains how to use and navigate the webpage produced by
        epydoc.
      - X{toc.html}: The top-level table of contents page.  This page
        is displayed in the upper-left frame, and provides links to
        toc-everything.html and the toc-I{module}-module.html files.  toc.html
        is not generated if the --no-frames option is used.
      - X{toc-everything.html}: The table of contents for the entire
        project.  This page is displayed in the lower-left frame, and
        provides links to every class, type, exception, function,
        and variable defined by the project.  toc-everything.html is
        not generated if the --no-frames option is used.
      - X{toc-I{module}-module.html}: The table of contents for a module.
        This page is displayed in the lower-left frame, and provides
        links to every class, type, exception, function, and variable
        defined by the module.  module is the complete dotted name of
        the module, such as sys or epydoc.epytext.  toc-m-module.html
        is not generated if the --no-frames option is used.
      - X{epydoc.css}: The CSS stylesheet used to display all HTML
        pages.

    @type _docmap: L{DocMap}
    @ivar _docmap: The documentation map, encoding the objects that
        should be documented.
    @type _prj_name: C{string}
    @ivar _prj_name: A name for the documentation (for the navbar).
    @type _prj_url: C{string}
    @ivar _prj_url: A URL for the documentation (for the navpar).
    @ivar _top_page: The URI of the top page.  This is the page shown
        in the main frame by the frames index; and the page that is
        used as the noframes index.
    @ivar _css: The name of a file containing a CSS stylesheet; or the
        name of a CSS stylesheet.
    @ivar _variable_linelen:  The maximum line length used for
        displaying the values of variables in the variable
        details sections.
    @ivar _variable_maxlines: The maximum number of lines that
        should be displayed for the value of a variable in the
        variable details section.
    @ivar _create_private_docs: Whether or not to create documentation
        pages that include information about private objects.
    @ivar _frames_index: Whether or not to create a frames-based
        table of contents for the documentation.
    @ivar _variable_tooltip_linelen: The maximum line length for
        variable value tooltips.
    """

    #////////////////////////////////////////////////////////////
    # Constructor
    #////////////////////////////////////////////////////////////
    
    def __init__(self, docmap, **kwargs):
        """
        Construct a new HTML formatter, using the given documentation map.
        @type docmap: L{DocMap}
        @param docmap: The documentation to output.
        
        @type prj_name: C{string}
        @keyword prj_name: The name of the project.  Defaults to
              none.
        @type prj_url: C{string}
        @keyword prj_url: The target for the project hopeage link on
              the navigation bar.  If C{prj_url} is not specified,
              then no hyperlink is created.
        @type prj_link: C{string}
        @keyword prj_link: The label for the project link on the
              navigation bar.  This link can contain arbitrary HTML
              code (e.g. images).  By default, a label is constructed
              from C{prj_name}.
        @type top: C{string}
        @keyword top: The top page for the documentation.  This
              is the default page shown main frame, when frames are
              enabled.  C{top} can be a URL, the name of a
              module, the name of a class, or one of the special
              strings C{"trees.html"}, C{"indices.html"}, or
              C{"help.html"}.  By default, the top-level package or
              module is used, if there is one; otherwise, C{"trees"}
              is used.
        @type css: C{string}
        @keyword css: The CSS stylesheet file.  If C{css} is a file
              name, then the specified file's conents will be used.
              Otherwise, if C{css} is the name of a CSS stylesheet in
              L{epydoc.css}, then that stylesheet will be used.
              Otherwise, an error is reported.  If no stylesheet is
              specified, then the default stylesheet is used.
        @type private_css: C{string}
        @keyword private_css: The CSS stylesheet file for the private
              API documentation.  If C{css} is a file name, then the
              specified file's conents will be used.  Otherwise, if
              C{css} is the name of a CSS stylesheet in L{epydoc.css},
              then that stylesheet will be used.  Otherwise, an error
              is reported.  If no stylesheet is specified, then the
              private API documentation will use the same stylesheet
              as the public API documentation.
        @type help: C{string}
        @keyword help: The name of the help file.  If no help file is
              specified, then the default help file will be used.
        @type private: C{boolean}
        @keyword private: Whether to create documentation for private
              objects.  By default, private objects are documented.
        @type frames: C{boolean})
        @keyword frames: Whether to create a frames-based table of
              contents.  By default, it is produced.
        @type show_imports: C{boolean}
        @keyword show_imports: Whether or not to display lists of
              imported functions and classes.  By default, they are
              not shown.
        @type index_parameters: C{boolean}
        @keyword index_parameters: Whether or not to include function
              parameters in the identifier index.  By default, they
              are not included.
        @type variable_maxlines: C{int}
        @keyword variable_maxlines: The maximum number of lines that
              should be displayed for the value of a variable in the
              variable details section.  By default, 8 lines are
              displayed.
        @type variable_linelength: C{int}
        @keyword variable_linelength: The maximum line length used for
              displaying the values of variables in the variable
              details sections.  If a line is longer than this length,
              then it will be wrapped to the next line.  The default
              line length is 70 characters.
        @type variable_summary_linelength: C{int}
        @keyword variable_summary_linelength: The maximum line length
              used for displaying the values of variables in the summary
              section.  If a line is longer than this length, then it
              will be truncated.  The default is 40 characters.
        @type variable_tooltip_linelength: C{int}
        @keyword variable_tooltip_linelength: The maximum line length
              used for tooltips for the values of variables.  If a
              line is longer than this length, then it will be
              truncated.  The default is 600 characters.
        @type property_function_linelength: C{int}
        @keyword property_function_linelength: The maximum line length
              used to dispaly property functions (C{fget}, C{fset}, and
              C{fdel}) that contain something other than a function
              object.  The dfeault length is 40 characters.
        @type inheritance: C{string}
        @keyword inheritance: How inherited objects should be displayed.
              If C{inheritance='grouped'}, then inherited objects are
              gathered into groups; if C{inheritance='listed'}, then
              inherited objects are listed in a short list at the
              end of their group; if C{inheritance='included'}, then
              inherited objects are mixed in with non-inherited
              objects.  The default is 'grouped'.
        """
        self._docmap = docmap

        # Process keyword arguments.
        self._prj_name = kwargs.get('prj_name', None)
        self._prj_url = kwargs.get('prj_url', None)
        self._prj_link = kwargs.get('prj_link', None)
        self._create_private_docs = kwargs.get('private', 1)
        self._top_page = self._find_top_page(kwargs.get('top', None))
        self._css = kwargs.get('css')
        self._private_css = kwargs.get('private_css') or self._css
        self._helpfile = kwargs.get('help', None)
        self._frames_index = kwargs.get('frames', 1)
        self._show_imports = kwargs.get('show_imports', 0)
        self._index_parameters = kwargs.get('index_parameters', 0)
        self._propfunc_linelen = kwargs.get('property_function_linelength', 40)
        self._variable_maxlines = kwargs.get('variable_maxlines', 8)
        self._variable_linelen = kwargs.get('variable_linelength', 70)
        self._variable_summary_linelen = \
                         kwargs.get('variable_summary_linelength', 55)
        self._variable_tooltip_linelen = \
                         kwargs.get('variable_tooltip_linelength', 600)
        self._inheritance = kwargs.get('inheritance', 'grouped')

        # Create the project homepage link, if it was not specified.
        if (self._prj_name or self._prj_url) and not self._prj_link:
            name = self._prj_name or 'Project Homepage'
            name = name.replace('&', '&amp;')
            name = name.replace('<', '&lt;')
            name = name.replace('>', '&gt;')
            name = name.replace(' ', '&nbsp;')
            self._prj_link = name

        # Add a hyperlink to _prj_url, if _prj_link doesn't already
        # contain any hyperlinks.
        if (self._prj_link and self._prj_url and
            not re.search(r'<a[^>]*\shref', self._prj_link)):
            self._prj_link = ('<a class="navbar" target="_top" href="'+
                              self._prj_url+'">'+self._prj_link+'</a>')

    #////////////////////////////////////////////////////////////
    # Output file count
    #////////////////////////////////////////////////////////////
    
    def num_files(self):
        """
        @return: The number of files that this C{HTMLFormatter} will
            generate.
        @rtype: C{int}
        """
        # Basic files (index.html, tree, indices, help, css, toc,
        # toc-everything, frames)
        n = 8

        for uid in self._docmap.keys():
            # Module and class API files
            if (uid.is_module() or uid.is_class()):
                if self._create_private_docs: n += 1
                elif uid.is_public(): n += 1

            # Module TOC files.
            if uid.is_module(): 
                if self._create_private_docs: n += 1
                elif uid.is_public(): n += 1
        return n

    #////////////////////////////////////////////////////////////
    # Write (and its helpers)
    #////////////////////////////////////////////////////////////
    
    def write(self, directory=None, progress_callback=None):
        """
        Write the documentation to the given directory.

        @type directory: C{string}
        @param directory: The directory to which output should be
            written.  If no directory is specified, output will be
            written to the current directory.  If the directory does
            not exist, it will be created.
        @type progress_callback: C{function}
        @param progress_callback: A callback function that is called
            before each file is written, with the name of the created
            file.
        @rtype: C{None}
        @raise OSError: If C{directory} cannot be created,
        @raise OSError: If any file cannot be created or written to.
        """        
        # Keep track of failed xrefs, and report them at the end.
        self._failed_xrefs = {}

        # Create destination directories, if necessary
        if not directory: directory = os.curdir
        public_directory = os.path.join(directory, 'public')
        private_directory = os.path.join(directory, 'private')
        self._mkdir(directory)
        if self._create_private_docs:
            self._mkdir(public_directory)
            self._mkdir(private_directory)

        # Write the CSS files.
        if progress_callback: progress_callback('epydoc.css')
        if self._create_private_docs:
            self._write_css(public_directory, self._css)
            self._write_css(private_directory, self._private_css)
            self._write_css(directory, self._css)
        else:
            self._write_css(directory, self._css)

        # Write the object documentation.
        for (uid, doc) in self._docmap.items():
            if not (isinstance(doc, ModuleDoc) or
                    isinstance(doc, ClassDoc)): continue
            filename = self._uid_to_filename(uid)
            if isinstance(doc, ModuleDoc):
                self._write(self._write_module, directory, filename,
                             progress_callback, uid.is_public(), uid, doc)
            else:
                self._write(self._write_class, directory, filename,
                             progress_callback, uid.is_public(), uid, doc)
        
        # Write the term & identifier indices
        self._write(self._write_indices, directory,
                     'indices.html', progress_callback, 1)
        
        # Write the trees file (package & class hierarchies)
        self._write(self._write_trees, directory,
                     'trees.html', progress_callback, 1)
        
        # Write the help file.
        self._write(self._write_help, directory,
                     'help.html', progress_callback, 1)
        
        # Write the frames-based table of contents.
        self._write(self._write_frames, directory, 'frames.html',
                     progress_callback, 1)
        self._write(self._write_toc, directory, 'toc.html',
                     progress_callback, 1)
        self._write(self._write_project_toc, directory,
                    'toc-everything.html', progress_callback, 1)
        for (uid, doc) in self._docmap.items():
            if uid.is_module():
                filename = 'toc-%s' % self._uid_to_filename(uid)
                self._write(self._write_module_toc, directory, filename,
                             progress_callback, uid.is_public(), uid, doc)
        
        # Write the index.html files.
        if progress_callback: progress_callback('index.html')
        if self._create_private_docs:
            self._write_index(public_directory, frombase=0)
            self._write_index(private_directory, frombase=0)
            self._write_index(directory, frombase=1)
        else:
            self._write_index(directory, frombase=0)

        # Report any failed crossreferences
        if self._failed_xrefs:
            estr = 'Warning: Failed identifier crossreference targets:\n'
            failed_identifiers = self._failed_xrefs.keys()
            failed_identifiers.sort(lambda a,b: cmp(a[1],b[1]))
            for identifier in failed_identifiers:
                uids = self._failed_xrefs[identifier].keys()
                uids.sort()
                estr += '    - %s' % identifier
                if len(uids)==1 and len(identifier)+len(uid.name())+14 < 70:
                    estr += ' (from %s)\n' % uids[0].name()
                else:
                    estr += '\n'
                    for uid in uids:
                        estr += '      (from %s)\n' % uid.name()
            if sys.stderr.softspace: print >>sys.stderr
            print >>sys.stderr, estr

    def _write(self, write_func, directory, filename,
               progress_callback, is_public, *args):
        """
        A helper for L{write}, that creates new public and private
        streams for a given filename, and delegates writing to
        C{write_func}.  If L{_create_private_docs} is true, then the
        streams are created in the C{'public'} and C{'private'}
        subdirectories of C{directory}.  If it's false, then the
        public stream is created in C{directory}, and a L{_DevNull} is
        used for the private stream.

        @param write_func: The output function to delegate to.  It is
            called with a public stream, a private stream, and any
            arguments given in C{args}.
        @param directory: The base directory for the output files
        @param filename: The filename of the file(s) that C{write_func}
            should write to.
        @param progress_callback: A progress callback function.  If it
            is not C{None}, then it is called with C{filename} before
            C{write_func} is called.
        @param is_public: Whether this is a public page.  If it is
            not a public page, then a C{_DevNull} will be used for
            the public stream.  (Or if it is not a public page and
            C{_create_private_docs} is false, then immediately
            return.)
        @param args: Extra arguments for C{write_func}
        @rtype: C{None}
        """
        # If we're only writing public pages, then ignore private ones.
        if not self._create_private_docs and not is_public:
            return
        
        # Display our progress.
        if progress_callback: progress_callback(filename)
        
        if self._create_private_docs:
            # If it's a public page, then write to both public &
            # private; otherwise, just write to private.
            private = open(os.path.join(directory, 'private', filename), 'w')
            if is_public:
                public = open(os.path.join(directory, 'public', filename), 'w')
            else:
                public = _DevNull()
        else:
            # Just write to public; ignore output to private.
            public = open(os.path.join(directory, filename), 'w')
            private = _DevNull()
        write_func(public, private, *args)
        public.close()
        private.close()

    def _mkdir(self, directory):
        """
        If the given directory does not exist, then attempt to create it.
        @rtype: C{None}
        """
        if not os.path.isdir(directory):
            if os.path.exists(directory):
                raise OSError('%r is not a directory' % directory)
            os.mkdir(directory)
        
    #////////////////////////////////////////////////////////////
    # HTML page generation
    #////////////////////////////////////////////////////////////
    # Each of these functions creates a single HTML file, and returns
    # it as a string.

    def _write_index(self, directory, frombase):
        """
        Write an C{index.html} file in the given directory.  The
        contents of this file are copied or linked from an existing
        page.  The page used is determined by L{_frames_index} and
        L{_top_page}:
            - If L{_frames_index} is true, then C{frames.html} is
              copied.
            - Otherwise, the page specified by L{_top_page} is
              copied.
        @param frombase: True if this is the index file for the base
            directory when we are generating both public and private
            documentation.  In this case, all local hyperlinks should
            be changed to point into the C{public} subdirectory.
        @type frombase: C{boolean}
        """
        filename = os.path.join(directory, 'index.html')
        if self._frames_index: top = 'frames.html'
        else: top = self._top_page

        # Copy the non-frames index file from top, if it's internal.
        if top[:5] != 'http:' and '/' not in top:
            try:
                # Read top into str.
                if frombase:
                    pubdir = os.path.join(directory, 'public')
                    topfile = os.path.join(pubdir, top)
                else:
                    topfile = os.path.join(directory, top)
                str = open(topfile, 'r').read()

                # Redirect links, if appropriate.
                if frombase:
                    # Excluding ":" makes sure we don't get any links
                    # to external targets (like href="http://...")
                    INTERNAL_LINK = r'(<a[^>]+href=")([^:">]+"[^>]*>)'
                    FRAME_LINK = r'(<frame[^>]+src=")([^:">]+"[^>]*>)'
                    str = re.sub(INTERNAL_LINK, r'\1public/\2', str)
                    str = re.sub(FRAME_LINK, r'\1public/\2', str)

                # Write the output file.
                open(filename, 'w').write(str)
                return
            except:
                if sys.stderr.softspace: print >>sys.stderr
                estr = 'Warning: error copying index; using a redirect page'
                print >>sys.stderr, estr
                if frombase: top = 'public/%s' % top

        # Use a redirect if top is external, or if we faild to copy.
        name = self._prj_name or 'this project'
        open(filename, 'w').write(REDIRECT_INDEX % (top, top, name))

    def _write_css(self, directory, cssname):
        """
        Write the CSS stylesheet in the given directory.  If
        C{cssname} contains a stylesheet file or name (from
        L{epydoc.css}), then use that stylesheet; otherwise, if a
        stylesheet file already exists, use that stylesheet.
        Otherwise, use the default stylesheet.

        @rtype: C{None}
        """
        filename = os.path.join(directory, 'epydoc.css')
        
        # Get the contents for the stylesheet file.  If none was
        # specified, and a stylesheet is already present, then don't
        # do anything.
        if cssname is None:
            if os.path.exists(filename):
                return
            else: css = STYLESHEETS['default'][0]
        else:
            if os.path.exists(cssname):
                try: css = open(cssname).read()
                except: raise IOError("Can't open CSS file: %r" % cssname)
            elif STYLESHEETS.has_key(cssname):
                css = STYLESHEETS[cssname][0]
            else:
                raise IOError("Can't find CSS file: %r" % cssname)

        # Write the stylesheet.
        cssfile = open(filename, 'w')
        cssfile.write(css)
        cssfile.close()
                       
    def _write_module(self, public, private, uid, doc, body_only=False):
        """
        Write an HTML page describing the given module to the given
        streams.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param uid: The UID of the module to document.
        @param doc: The L{ModuleDoc} of the module to document.
        """
        if not body_only:
            # Write the header & navigation bar.
            str = self._header(uid.name())
            public.write(str); private.write(str)
            self._write_navbar(public, private, uid)
            self._write_breadcrumbs(public, private, uid)

        # Get the module name.
        if uid.is_package(): str = self._start_of('Package Description')
        else: str = self._start_of('Module Description')
        if uid.is_package():
            str += '<h2 class="package">Package '+uid.name()+'</h2>\n\n'
        else:
            str += '<h2 class="module">Module '+uid.name()+'</h2>\n\n'

        # Get the module's description.
        if doc.descr():
            str += self._docstring_to_html(doc.descr(), uid) + '<hr/>\n'

        # Get the version, author, warnings, requirements, notes, etc.
        str += self._standard_fields(doc)

        # Write the module name, descr, and standard fields to the
        # output streams.
        public.write(str); private.write(str)

        # If it's a package, write a list of sub-modules.
        if doc.ispackage():
            self._write_module_list(public, private, doc, doc.modules())

        # Write the summaries for classes, exceptions, functions, and
        # variables contained in the module.
        (classes,excepts) = self._split_classes(doc.classes())
        self._write_class_summary(public, private, doc, classes, 'Classes')
        self._write_class_summary(public, private, doc, excepts, 'Exceptions')
        self._write_func_summary(public, private, doc, doc.functions(), 
                                 'Function Summary')
        self._write_var_summary(public, private, doc, doc.variables(), 
                                'Variable Summary')
        
        # Write a list of all imported objects.
        if self._show_imports:
            self._write_imports(public, private, doc)

        # Write details for the functions and variables.
        self._write_func_details(public, private, doc, doc.functions())
        self._write_var_details(public, private, doc, doc.variables())

        if not body_only:
            # Write another navigation bar and the footer.
            self._write_navbar(public, private, uid)
            footer = self._footer()
            public.write(footer); private.write(footer)

    def _write_class(self, public, private, uid, doc, body_only=False):
        """
        Write an HTML page describing the given module to the given
        streams.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param uid: The UID of the module to document.
        @param doc: The L{ModuleDoc} of the module to document.
        """
        if not body_only:
            # Write the header & navigation bar
            str = self._header(uid.name())
            public.write(str); private.write(str)
            self._write_navbar(public, private, uid)
            self._write_breadcrumbs(public, private, uid)
        
        # Get the class name.
        str = self._start_of('Class Description')
        if uid.is_type():
            str += '<h2 class="class">Type '
        else:
            str += '<h2 class="class">Class '
        str += uid.shortname()+'</h2>\n\n'

        # Get the base class tree
        if doc.bases():
            str += '<pre class="base-tree">\n' 
            str += self._base_tree(uid) 
            str += '</pre><br />\n\n'

        # Write the class name & base class tree.
        public.write(str); private.write(str)

        # Write the class's known subclasses
        subclasses = doc.subclasses()
        if subclasses:
            header = '<dl><dt><b>Known Subclasses:</b></dt>\n<dd>\n    '
            _write_if_nonempty(public, private, subclasses, header)
            private.write(',\n    '.join([self._link_to_html(s)
                                          for s in subclasses]))
            public.write(',\n    '.join([self._link_to_html(s)
                                         for s in subclasses
                                         if s.is_public()]))
            footer = '</dd></dl>\n\n'
            _write_if_nonempty(public, private, subclasses, footer)

        # Write the class's description and version, author, etc.
        str = '<hr/>\n\n'
        if doc.descr():
            str += '%s<hr/>\n\n' % self._docstring_to_html(doc.descr(), uid)
        str += self._standard_fields(doc)
        public.write(str); private.write(str)

        # Write the summaries for methods, instance variables, and
        # class variables contained in the class.
        self._write_func_summary(public, private, doc, doc.allmethods(), 
                                 'Method Summary')
        self._write_property_summary(public, private, doc, doc.properties(),
                                     'Property Summary')
        self._write_var_summary(public, private, doc, doc.ivariables(), 
                                'Instance Variable Summary')
        self._write_var_summary(public, private, doc, doc.cvariables(),
                                'Class Variable Summary')

        # Write details for methods
        if doc.staticmethods() or doc.classmethods():
            self._write_func_details(public, private, doc, doc.methods(),
                                     'Instance Method Details')
            self._write_func_details(public, private, doc, doc.staticmethods(),
                                     'Static Method Details')
            self._write_func_details(public, private, doc, doc.classmethods(),
                                     'Class Method Details')
        else:
            self._write_func_details(public, private, doc, doc.methods(),
                                     'Method Details')

        # Write details for variables.
        self._write_property_details(public, private, doc, doc.properties(),
                                     'Property Details')
        self._write_var_details(public, private, doc, doc.ivariables(),
                                'Instance Variable Details')
        self._write_var_details(public, private, doc, doc.cvariables(),
                                'Class Variable Details')

        if not body_only:
            # Write another navigation bar and the footer.
            self._write_navbar(public, private, uid)
            footer = self._footer()
            public.write(footer); private.write(footer)

    def _write_help(self, public, private):
        """
        Write an HTML help file to the given streams.  If
        C{self._helpfile} contains a help file, then use it;
        otherwise, use the default helpfile from L{epydoc.help}.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Get the contents of the help file.
        if self._helpfile:
            if os.path.exists(self._helpfile):
                try: help = open(self._helpfile).read()
                except: raise IOError("Can't open help file: %r" %
                                      self._helpfile)
            else:
                raise IOError("Can't find help file: %r" % self._helpfile)
        else:
            if self._prj_name: thisprj = self._prj_name
            else: thisprj = 'this project'
            help = HTML_HELP % {'this_project':thisprj}

        # Write the header & navbar
        header = self._header('Help')
        public.write(header); private.write(header)
        self._write_navbar(public, private, 'help')
        self._write_breadcrumbs(public, private, 'help')
        
        # Write the help file.
        public.write(help)
        private.write(help)

        # Write the footer
        self._write_navbar(public, private, 'help')
        footer = self._footer()
        public.write(footer); private.write(footer)

    def _write_trees(self, public, private):
        """
        Write an HTML page containing the module and class hierarchies
        to the given streams.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Write the header and navigation bar
        header = self._header('Module and Class Hierarchies')
        public.write(header); private.write(header)
        self._write_navbar(public, private, 'trees')
        self._write_breadcrumbs(public, private, 'trees')

        # Write the module hierarchy
        str = self._start_of('Module Hierarchy')
        str += '<h2>Module Hierarchy</h2>\n'
        public.write(str); private.write(str)
        self._write_module_tree(public, private)

        # Does the project define any classes?
        defines_classes = 0
        for uid in self._docmap.keys():
            if uid.is_class(): defines_classes = 1; break

        # Write the class hierarchy
        classes = [u for u in self._docmap.keys() if u.is_class()]
        str = self._start_of('Class Hierarchy')
        str += '<h2>Class Hierarchy</h2>\n'
        _write_if_nonempty(public, private, classes, str)
        self._write_class_tree(public, private)

        # Write another navigation bar and the footer.
        self._write_navbar(public, private, 'tree')
        footer = self._footer()
        public.write(footer); private.write(footer)

    def _write_indices(self, public, private):
        """
        Write an HTML page containing the term and identifier indices
        to the given streams.
        @bug: If there are private indexed terms, but no public
            indexed terms, then this function will still write a
            header for the Term Index to the public stream.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Write the header and navigation bar.
        header = self._header('Index')
        public.write(header); private.write(header)
        self._write_navbar(public, private, 'indices')
        self._write_breadcrumbs(public, private, 'indices')
        public.write('<br />\n'); private.write('<br />\n')

        # Write the term index. 
        index = self._extract_term_index()
        if index:
            header = self._table_header('Term Index', 'index')
            public.write(header); private.write(header)
            for (key, term, links) in index:
                str = '  <tr><td width="15%">' + term.to_plaintext(None)
                str += '</td>\n    <td>'
                _write_if_nonempty(public, private, links, str)
                for link in links:
                    str = ('<i><a href="%s#%s">%s</a></i>, ' %
                            (self._uid_to_uri(link.target()),
                             key, link.target().name()))
                str = str[:-2] + '</tr></td>\n'
                _write_if_nonempty(public, private, links, str)
            footer = '</table>\n<br />\n'
            public.write(footer); private.write(footer)

        # Write the identifier index
        identifiers = self._extract_identifier_index()
        if identifiers:
            header = self._table_header('Identifier Index', 'index')
            _write_if_nonempty(public, private, identifiers, header)
            for uid in identifiers:
                href = self._uid_to_href(uid, uid.shortname())

                # Create the description string.
                if uid.is_package(): descr = 'Package'
                elif uid.is_module(): descr = 'Module'
                elif uid.is_class(): descr = 'Class'
                elif uid.is_variable(): descr = 'Variable'
                elif uid.is_method() or uid.is_builtin_method():
                    descr = 'Method'
                elif uid.is_function() or uid.is_builtin_function():
                    descr = 'Function'
                puid = uid.parent()
                if puid:
                    if puid.is_package(): descr +=' in package '
                    elif puid.is_module(): descr +=' in module '
                    elif puid.is_class(): descr +=' in class '
                    else: descr +=' in '
                    descr += self._uid_to_href(uid.parent())

                # Write the index entry.
                str = ('  <tr><td width="15%%">%s</td>\n' % href +
                       '    <td>%s</td></tr>\n' % descr)
                private.write(str)
                if uid.is_public(): public.write(str)

            footer = '</table>\n<br />\n'
            _write_if_nonempty(public, private, identifiers, footer)

        # Navigation bar and footer.
        self._write_navbar(public, private, 'indices')
        footer = self._footer()
        public.write(footer); private.write(footer)

    def _write_frames(self, public, private):
        """
        Write the frames index file for the frames-based table of
        contents to the given streams.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        prj_name = self._prj_name or "API Documentation"
        str = FRAMES_INDEX % (prj_name, self._top_page)
        public.write(str); private.write(str)

    def _write_toc(self, public, private):
        """
        Write an HTML page containing the top-level table of contents.
        This page is used to select a module table of contents page,
        or the \"everything\" table of contents page.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Sort the list of uids by full name (case insensitive)
        decorated = [(u.name().lower(), u) for u in self._docmap.keys()
                     if u.is_module()]
        decorated.sort()
        uids = [d[-1] for d in decorated]

        # Write the header.
        str = self._header('Table of Contents')
        str += ('<center><font size="+1"><b>Table&nbsp;of&nbsp;'
                 'Contents</b></font></center>\n<hr>\n')
        public.write(str); private.write(str)

        # Write a pointer to the project table of contents ("everything")
        str = ('<a target="moduleFrame" href="%s">%s</a><br />\n' %
                ('toc-everything.html', 'Everything'))
        public.write(str); private.write(str)

        # Write pointers to the packages' tables of contents
        str = self._start_of('Packages')
        str += '<br /><font size="+1"><b>Packages</b></font><br />\n'
        public.write(str); private.write(str)
        for uid in uids:
            if uid.is_package():
                str = ('<a target="moduleFrame" href="toc-%s">'+
                       '%s</a><br />\n') % (self._uid_to_filename(uid), uid)
                private.write(str)
                if uid.is_public(): public.write(str)

        # Write pointers to the modules' tables of contents
        str = self._start_of('Modules')
        str += '<br /><font size="+1"><b>Modules</b></font><br />\n'
        public.write(str); private.write(str)
        for uid in uids:
            if uid.is_module() and not uid.is_package():
                str = ('<a target="moduleFrame" href="toc-%s">'+
                       '%s</a><br />\n') % (self._uid_to_filename(uid), uid)
                private.write(str)
                if uid.is_public(): public.write(str)
                
        # Write the private/public link.
        if self._create_private_docs:
            str = '\n<br /><hr>\n'
            public.write(str); private.write(str)
            where = 'toc'
            public.write(self._public_private_link(where, from_private=0))
            private.write(self._public_private_link(where, from_private=1))

        # Write the footer.
        str = '\n</body>\n</html>\n'
        public.write(str); private.write(str)

    def _write_project_toc(self, public, private):
        """
        Write an HTML page containing the table of contents page for
        the whole project to the given streams.  This page lists the
        classes, exceptions, functions, and variables defined by any
        module or package in the project.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Write the header.
        str = self._header('Everything')
        str += ('<center><font size="+1"><b>Everything</b>' +
                 '</font></center>\n<hr>\n')
        public.write(str); private.write(str)

        # Find classes & exceptions
        uids = self._docmap.keys()
        classes = [Link(c.name(),c) for c in uids if c.is_class()]
        (classes,excepts) = self._split_classes(classes)

        # Find functions & variables (but not methods)
        funcs = []; vars = []
        for uid in uids:
            doc = self._docmap[uid]
            if isinstance(doc, ModuleDoc):
                funcs += doc.functions()
                vars += doc.variables()
        
        # Only include objects that come from modules that we
        # documented.  In particular, do not include base classes,
        # inherited methods, and imported objects from other modules
        # in the index.
        is_documented = self._docmap.has_key
        classes = [c for c in classes
                   if is_documented(c.target().module())]
        excepts = [e for e in excepts
                   if is_documented(e.target().module())]
        funcs = [f for f in funcs
                 if is_documented(f.target().module())]
        vars = [v for v in vars
                if is_documented(v.uid().module())]

        # Write the lists of objects.
        self._write_toc_section(public, private, 'All Classes', classes)
        self._write_toc_section(public, private, 'All Exceptions', excepts)
        self._write_toc_section(public, private, 'All Functions', funcs)
        self._write_toc_section(public, private, 'All Variables', vars)

        # Write the private/public link.
        if self._create_private_docs:
            str = '\n<hr>\n'
            public.write(str); private.write(str)
            where = 'toc-everything'
            public.write(self._public_private_link(where, from_private=0))
            private.write(self._public_private_link(where, from_private=1))

        # Write the footer.
        str = '\n</body>\n</html>\n'
        public.write(str); private.write(str)
        
    def _write_module_toc(self, public, private, uid, doc):
        """
        Write an HTML page containing the table of contents page for
        the given module to the given streams.  This page lists the
        modules, classes, exceptions, functions, and variables defined
        by the module.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Split classes into classes & exceptions.
        doc = self._docmap[uid]
        (classes,excepts) = self._split_classes(doc.classes())

        # Write the header.
        str = self._header(uid.name())
        str += (('<center><font size="+1"><b><a target="mainFrame" '+
                'href="%s">%s</a></b></font></center>\n<hr>\n')
                % (self._uid_to_uri(uid), uid.shortname()))
        public.write(str); private.write(str)
        
        # Write the lists of objects.
        if uid.is_package():
            self._write_toc_section(public, private, 'Modules', doc.modules())
        self._write_toc_section(public, private, 'Classes', classes)
        self._write_toc_section(public, private, 'Exceptions', excepts)
        self._write_toc_section(public, private, 'Functions', doc.functions())
        self._write_toc_section(public, private, 'Variables', doc.variables())
                                 
        # Write the private/public link.
        if self._create_private_docs:
            str = '\n<hr>\n'
            public.write(str); private.write(str)
            public.write(self._public_private_link(uid, toc=1,
                                                   from_private=0))
            private.write(self._public_private_link(uid, toc=1,
                                                    from_private=1))

        # Write the footer.
        str = '\n</body>\n</html>\n'
        public.write(str); private.write(str)
    
    #////////////////////////////////////////////////////////////
    # Navigation bar & breadcrumbs
    #////////////////////////////////////////////////////////////
    # The navigation bar is placed at the top & bottom of every HTML
    # page.

    def _write_navbar(self, public, private, where=None):
        """
        Write the HTML code for the navigation bar to the given
        streams.  The navigation bar typically looks like::
            
                [ Home Trees Index Help            Project ]
                
        @param where: An identifier indicating what page we're
            creating a navigation bar for.  This is either a UID
            (for an object documentation page); or one of the strings
            C{'tree'}, C{'index'}, and C{'help'}.
        @type where: C{UID} or C{string}
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Construct the navbar.  This is the same for both public &
        # private, so put it together with a single StringIO object,
        # and then write it to both streams.
        sio = StringIO()
        sio.write(self._start_of('Navbar'))
        sio.write('<table class="navbar" border="0" width="100%"')
        sio.write(' cellpadding="0" bgcolor="#a0c0ff" cellspacing="0">\n')
        sio.write('  <tr valign="center">\n')

        # The "Home" link
        if self._top_page in ('trees.html', 'indices.html',
                              'help.html'):
            pass # We already have a link for these.
        elif (isinstance(where, UID) and
            self._uid_to_uri(where) == self._top_page):
            sio.write('    <th bgcolor="#70b0f0" class="navselect">&nbsp;')
            sio.write('&nbsp;&nbsp;Home&nbsp;&nbsp;&nbsp;</th>\n')
        else:
            sio.write('    <th class="navbar">&nbsp;&nbsp;&nbsp;<a ')
            sio.write('class="navbar" href="%s">Home</a>' % self._top_page)
            sio.write('&nbsp;&nbsp;&nbsp;</th>\n' )

        # The "Tree" link
        if where == 'trees':
            sio.write('    <th bgcolor="#70b0f0" class="navselect">&nbsp;')
            sio.write('&nbsp;&nbsp;Trees&nbsp;&nbsp;&nbsp;</th>\n')
        else:
            sio.write('    <th class="navbar">&nbsp;&nbsp;&nbsp;')
            sio.write('<a class="navbar" href="trees.html">Trees</a>')
            sio.write('&nbsp;&nbsp;&nbsp;</th>\n')

        # The "Index" link
        if where == 'indices':
            sio.write('    <th bgcolor="#70b0f0" class="navselect">&nbsp;')
            sio.write('&nbsp;&nbsp;Index&nbsp;&nbsp;&nbsp;</th>\n')
        else:
            sio.write('    <th class="navbar">&nbsp;&nbsp;&nbsp;')
            sio.write('<a class="navbar" href="indices.html">Index</a>')
            sio.write('&nbsp;&nbsp;&nbsp;</th>\n')

        # The "Help" link
        if where == 'help':
            sio.write('    <th bgcolor="#70b0f0" class="navselect">&nbsp;')
            sio.write('&nbsp;&nbsp;Help&nbsp;&nbsp;&nbsp;</th>\n')
        else:
            sio.write('    <th class="navbar">&nbsp;&nbsp;&nbsp;')
            sio.write('<a class="navbar" href="help.html">Help</a>')
            sio.write('&nbsp;&nbsp;&nbsp;</th>\n')

        # The project homepage link.
        if self._prj_link:
            sio.write('    <th class="navbar" align="right" width="100%">\n')
            sio.write('      <table border="0" cellpadding="0" cellspacing=')
            sio.write('"0">\n      <tr><th class="navbar" align="center">\n')
            sio.write('        <p class="nomargin">\n          ')
            sio.write(self._prj_link)
            sio.write('\n      </p></th></tr></table>\n')
            sio.write('    </th>\n')
        else:
            sio.write('    <th class="navbar" width="100%"></th>\n')
        sio.write('  </tr>\n</table>\n')

        # Write the navbar to both streams.
        str = sio.getvalue()
        public.write(str); private.write(str)

    def _write_breadcrumbs(self, public, private, where=None):
        """
        Write the HTML code for the breadcrumbs line to the given
        streams.  The breadcrumbs line is an invisible table with a
        list of pointers to the current object's ancestors on the
        left; and the show/hide private selector and the
        frames/noframes selector on the right.
        
        @param where: An identifier indicating what page we're
            creating a navigation bar for.  This is either a UID
            (for an object documentation page); or a string.  If
            it is a UID, then a list of pointers to its ancestors
            is displayed.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Write the breadcrumbs (pointers to ancestors)
        str = '<table width="100%" cellpadding="0" cellspacing="0">\n'
        str += '  <tr valign="top">\n    <td width="100%">\n'
        if isinstance(where, UID): str += self._breadcrumbs(where)
        str += '    </td>\n    <td>'
        str += '<table cellpadding="0" cellspacing="0">\n'
        public.write(str); private.write(str)

        # Write the public/private link
        if self._create_private_docs:
            link = self._public_private_link(where, from_private=0)
            public.write('      <tr><td align="right">%s</td></tr>\n' % link)
            link = self._public_private_link(where, from_private=1)
            private.write('      <tr><td align="right">%s</td></tr>\n' % link)

        # Write the frames/noframes link.
        str = ('      <tr><td align="right">%s</td></tr>\n' %
                self._frames_link(where))
        str += '    </table></td>\n'
        str += '</tr></table>\n'
        public.write(str); private.write(str)
            
    def _frames_link(self, where):
        """
        @return: HTML code for the frames/noframes selector.  This
            selector is used to turn the frames-based table of
            contents on or off.
        @rtype: C{string}
        @param where: An identifier indiciating the page we're
            creating a navigation bar for.  This is either a UID
            or the name of a file without the ".html" extension.
        """
        if isinstance(where, UID): uri = self._uid_to_uri(where)
        else: uri = where+'.html'
        return ('<font size="-2">[<a href="frames.html"'+
                'target="_top">frames</a>&nbsp;|&nbsp;<a href="'+uri+
                '" target="_top">no&nbsp;frames</a>]</font>')
    
    def _public_private_link(self, where, toc=0, from_private=0):
        """
        @return: HTML code for the show/hide private selector.  This
            selector is used to select whether private objects should
            be displayed.
        @rtype: C{string}
        @param where: An identifier indiciating the page we're
            creating a navigation bar for.  This is either a UID
            or the name of a file without the ".html" extension.
        """
        # For private pages, there's no corresponding public page.
        if isinstance(where, UID) and where.is_private():
            return ('<font size="-2">[<b>show&nbsp;private</b>' +
                    '&nbsp;|&nbsp;hide&nbsp;private]</font>')

        if isinstance(where, UID): uri = self._uid_to_filename(where)
        else: uri = where+'.html'
        if toc: uri = 'toc-'+uri
        
        if from_private:
            return ('<font size="-2">[show&nbsp;private&nbsp;|&nbsp;' +
                    '<a href="../public/' + uri + '">hide&nbsp;private' +
                    '</a>]</font>')
        else:
            return ('<font size="-2">[<a href="../private/' + uri +
                    '">show&nbsp;private</a>&nbsp;|&nbsp;hide&nbsp;' +
                    'private]</font>')
    
    def _breadcrumbs(self, uid):
        """
        @return: The HTML code for a series of links to the ancestors
            of C{uid}.
        @rtype: C{string}
        @param uid: The UID of the object whose ancestors should
            be listed.
        """
        # Generate the crumbs as a list in reverse order (from child
        # to parent), and then reverse them & combine them at the end.

        # Generate the crumb for uid itself.
        if uid.is_package(): crumbs = ['Package&nbsp;%s' % uid.shortname()]
        elif uid.is_module(): crumbs = ['Module&nbsp;%s' % uid.shortname()]
        elif uid.is_class(): crumbs = ['Class&nbsp;%s' % uid.shortname()]

        # Generate the crumbs for uid's ancestors.
        uid = uid.parent()
        while uid is not None:
            if uid.is_package(): label = 'Package&nbsp;%s' % uid.shortname()
            elif uid.is_module(): label = 'Module&nbsp;%s' % uid.shortname()
            elif uid.is_class(): label = 'Class&nbsp;%s' % uid.shortname()
            else: raise ValueError('Bad uid type for breadcrumbs')
            crumbs.append(self._uid_to_href(uid, label, code=0))
            uid = uid.parent()

        # Reverse, combine, and return the crumbs
        crumbs.reverse()
        str = '      <font size="-1"><b class="breadcrumbs">\n        '
        str += ' ::\n        '.join(crumbs)
        str += '\n      </b></font></br>\n'
        return str

    #////////////////////////////////////////////////////////////
    # Base class trees
    #////////////////////////////////////////////////////////////
    
    def _find_tree_width(self, uid):
        """
        Helper function for L{_base_tree}.
        @return: The width of a base tree, when drawn
            right-justified.  This is used by L{_base_tree} to
            determine how far to indent lines of the base tree.
        @rtype: C{int}
        """
        width = 2
        if self._docmap.has_key(uid):
            for base in self._docmap[uid].bases():
                width = max(width, len(base.name())+4)
                width = max(width, self._find_tree_width(base.target())+4)

        return width
        
    def _base_tree(self, uid, width=None, postfix=''):
        """
        @return: The HTML code for a class's base tree.  The tree is
            drawn 'upside-down' and right justified, to allow for
            multiple inheritance.
        @rtype: C{string}
        """
        if not self._docmap.has_key(uid): return ''
        if width == None:
            width = self._find_tree_width(uid)
        
        bases = self._docmap[uid].bases()
        
        if postfix == '':
            str = ' '*(width-2) + '<b>'+uid.shortname()+'</b>\n'
        else: str = ''
        for i in range(len(bases)-1, -1, -1):
            base = bases[i]
            str = (' '*(width-4-len(base.name())) +
                   self._link_to_html(base)+' --+'+postfix+'\n' + 
                   ' '*(width-4) +
                   '   |'+postfix+'\n' +
                   str)
            (t,w) = (base.target(), width)
            if i != 0:
                str = (self._base_tree(t, w-4, '   |'+postfix)+str)
            else:
                str = (self._base_tree(t, w-4, '    '+postfix)+str)
        ss = re.sub('<[^<>]+>','',str)
        return str
                
    #////////////////////////////////////////////////////////////
    # Class hierarchy trees
    #////////////////////////////////////////////////////////////

    def _write_class_tree(self, public, private):
        """
        Write HTML code for a nested list showing the base/subclass
        relationships between all documented classes.  Each element of
        the top-level list is a class with no (documented) bases; and
        under each class is listed all of its subclasses.  Note that
        in the case of multiple inheritance, a class may appear
        multiple times.  This is used by L{_write_trees} to write
        the class hierarchy.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @todo: For multiple inheritance, don't repeat subclasses the
            second time a class is mentioned; instead, link to the
            first mention.
        @bug: This function does not list public subclasses of
            private bases.
        """
        # Get a list of all documented classes
        uids = [uid for uid in self._docmap.keys() if uid.is_class()]
        if not uids: return
        uids.sort() # sort by full name.
        
        # Write the beginning-of-list tag
        _write_if_nonempty(public, private, uids, '<ul>\n')

        # Write entries for all top-level classes.
        for uid in uids:
            doc = self._docmap[uid]

            # Look for a documented base
            hasbase = 0
            for base in doc.bases():
                if self._docmap.has_key(base.target()):
                    hasbase = 1
                    break

            # If there's no documented base, then write an entry
            if not hasbase:
                self._write_class_tree_item(public, private, uid)

        # Write the end-of-list tag.
        _write_if_nonempty(public, private, uids, '</ul>\n')

    def _write_class_tree_item(self, public, private, uid,
                               depth=2, public_base=1):
        """
        Write HTML code for a list item describing a class and all of
        its subclasses.  This is used by L{_write_class_tree} to
        write the class hierarchy.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param uid: The C{UID} of the class tree to describe
        @param depth: The indentation depth.
        @param public_base: True if output should be generated for
            the public stream.  This is prevents public classes
            with private bases from being listed.
        @bug: This doesn't generate </li> close tags.
        """
        doc = self._docmap.get(uid)
        
        # Write the class's name & description
        str = ' '*depth + '<li> <b>' + self._uid_to_href(uid)+'</b>'
        if doc and doc.descr():
            str += ': <i>\n' + self._summary(doc, uid) + '</i>'
        str += '\n'
        private.write(str)
        if uid.is_public() and public_base: public.write(str)

        # If we have no docs, then that's all we can say.
        if doc is None: return

        # Find the list of subclasses.  If there are none, then we're done.
        subclasses = [l.target() for l in doc.subclasses()]
        if not subclasses: return

        # Write the beginning-of-list tag.
        str = ' '*depth + '  <ul>\n'
        if not public_base: private.write(str)
        else: _write_if_nonempty(public, private, subclasses, str)
        
        # Write the subclass list contents.
        for subclass in subclasses:
            self._write_class_tree_item(public, private, subclass, depth,
                                        public_base and uid.is_public())

        # Write the end-of-list tag.
        str = ' '*depth + '  </ul>\n'
        if not public_base: private.write(str)
        else: _write_if_nonempty(public, private, subclasses, str)
            
    #////////////////////////////////////////////////////////////
    # Module hierarchy trees
    #////////////////////////////////////////////////////////////
    
    def _write_module_tree(self, public, private):
        """
        Write HTML code for a nested list showing the submodule
        relationships between all documented packages and modules.
        This is used by L{_write_trees} to write the module
        hierarchy.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        """
        # Get a list of all documented modules
        uids = [uid for uid in self._docmap.keys() if uid.is_module()]
        if not uids: return
        uids.sort() # sort by full name.

        # Write the beginning-of-list tag
        _write_if_nonempty(public, private, uids, '<ul>\n')
        
        # Write entries for all top-level packages.
        for uid in uids:
            doc = self._docmap[uid]

            # If it's a top-level package, then write an entry.
            pkg = doc.package()
            if pkg is None or not self._docmap.has_key(pkg):
                self._write_module_tree_item(public, private, uid)
                
        # Write the end-of-list tag.
        _write_if_nonempty(public, private, uids, '</ul>\n')

    def _write_module_list(self, public, private, container, modules):
        """
        Write HTML code for a list of the submodules to the given
        streams.  This is used by L{_write_module} to list the
        submodules in a package.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param container: The package that contains the modules (as
            submodules).  C{container} is used to group the modules.
        @param modules: The modules that should be included in the list.
        """
        if len(modules) == 0: return

        # Divide the modules into groups
        groups = container.by_group(modules)

        # Write the table header.
        header = self._table_header('Submodules', 'details')
        _write_if_nonempty(public, private, modules, header)

        # Write entries for each group.
        for name, group in groups:            
            # Print a group header (except for the special group None,
            # which is always the first group).
            if name is not None:
                header = self._group_header(name)
                _write_if_nonempty(public, private, group, header)

            # Write a table row for each submodule.
            _write_if_nonempty(public, private, group, '  <tr><td><ul>\n')
            for module in group:
                self._write_module_tree_item(public, private, module.target())
            _write_if_nonempty(public, private, group, '  </ul></td></tr>\n')

        # Write the table footer.
        _write_if_nonempty(public, private, modules, '</table><br />\n\n')

    def _write_module_tree_item(self, public, private, uid, depth=0):
        """
        Write HTML for a list item describing a package/module and all
        of its submodules.  This is used by both L{_write_module_tree}
        and L{_write_module_list} to write package hierarchies.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param uid: The C{UID} of the module to describe
        @param depth: The indentation depth.
        @bug: This doesn't generate </li> close tags.
        """
        doc = self._docmap.get(uid)

        # Write the module's name & description
        str = (' '*depth + '<li> <b>' +
               self._uid_to_href(uid, uid.shortname())+'</b>')
        if doc and doc.descr():
            summary = self._summary(doc, uid)
            if summary != '&nbsp;': str += ': <i>' + summary + '</i>'
        str += '\n'
        private.write(str)
        if uid.is_public(): public.write(str)

        # If we have no docs, then that's all we can say.
        if doc is None or not doc.ispackage(): return

        # Find the list of submoules.  If there are none, then we're done.
        submodules = [l.target() for l in doc.modules()]
        if not submodules: return

        # Write the beginning-of-list tag.
        _write_if_nonempty(public, private, submodules, ' '*depth+'  <ul>\n')

        # Write the submodule list contents.
        for module in submodules:
            self._write_module_tree_item(public, private, module, depth+4)

        # Write the end-of-list tag.
        _write_if_nonempty(public, private, submodules, ' '*depth+'  </ul>\n')
    
    #////////////////////////////////////////////////////////////
    # Class tables
    #////////////////////////////////////////////////////////////
    
    def _write_class_summary(self, public, private, container, classes,
                             heading='Class Summary'):
        """
        Write HTML code for a class summary table.  This is used
        by L{_write_module} to list the classes in a module.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param container: The module that contains the classes.
            C{container} is used to group the classes.
        @param classes: The classes that should be included in the list.
        """
        if len(classes) == 0: return

        # Divide the classes into groups
        groups = container.by_group(classes)
        
        # Write the table header
        header = self._table_header(heading, 'summary')
        _write_if_nonempty(public, private, classes, header)

        # Write entries for each group
        for name, group in groups:
            # Print a group header (except for the special group None,
            # which is always the first group).
            if name is not None:
                header = self._group_header(name)
                _write_if_nonempty(public, private, group, header)

            # Write a table row for each class.
            for cls in group:
                self._write_class_summary_row(public, private, cls)

        # Write the table footer
        _write_if_nonempty(public, private, classes, '</table><br />\n\n')

    def _write_class_summary_row(self, public, private, link):
        """
        Write HTML code for a row in the class summary table.
        Each row gives the name and summary of a single class.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param link: A link to the class that should be described
            by this row.  
        """
        cname = link.name()
        cuid = link.target()
        cdoc = self._docmap.get(cuid)

        # Get the summary of the class.
        if cdoc and cdoc.descr(): csum = self._summary(cdoc, cuid)
        else: csum = '&nbsp;'

        # Write a row for the class.
        str = '<tr><td width="15%">\n'
        str += '  <b>'+self._link_to_html(link)
        str += '</b></td>\n  <td>' + csum + '</td></tr>\n'
        private.write(str)
        if cuid.is_public(): public.write(str)

    #////////////////////////////////////////////////////////////
    # Function tables
    #////////////////////////////////////////////////////////////
    
    def _write_func_summary(self, public, private, container, functions, 
                            heading='Function Summary'):
        """
        Write HTML code for a function summary table.  This is used
        by L{_write_module} to list the functions in a module; and by
        L{_write_class} to list the methods in a class.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param container: The module or class that contains the
            functions.  C{container} is used to group the functions.
        @param functions: The functions that should be included in
            the list.
        """
        if len(functions) == 0: return

        # Divide the functions into groups
        groups = container.by_group(functions)

        # Write the table header
        header = self._table_header(heading, 'summary')
        _write_if_nonempty(public, private, functions, header)

        # Write entries for each group.  Note that every group
        # contains at least one func or inherited func.
        for name, group in groups:
            # Print a group header (except for the special group None,
            # which is always the first group).
            if name is not None:
                header = self._group_header(name)
                _write_if_nonempty(public, private, group, header)

            # Write a table row for each class.  Skip inherited functions
            # if _inheritance=='listed', since they'll be listed below.
            for function in group:
                self._write_func_summary_row(public, private, 
                                             function, container)

            # Add an inheritance list, if appropriate
            if (self._inheritance == 'listed' and
                isinstance(container, ClassDoc)):
                self._write_inheritance_list(public, private, group,
                                             container)

        # Write the table footer
        _write_if_nonempty(public, private, functions, '</table><br />\n\n')

    def _write_func_summary_row(self, public, private,
                                function, container):
        """
        Write HTML code for a row in the function summary table.  Each
        row gives a breif description of a single function.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param function: The function that should be described by this row.
        @type function: L{Link}
        """
        fuid = function.target()
        fname = function.name()
        fdoc = self._docmap.get(fuid)

        # Is it an inherited function?
        cuid = container.uid()
        inherited = (cuid.is_class() and fuid.cls() != cuid)

        # If it's inherited & we're listing inheritance, then don't
        # write a table row for it; it'll be included in the list.
        if inherited and self._inheritance == 'listed': return

        # If the method doesn't define a docstring, then try to find
        # an overridden ancestor that does.  (This lets methods
        # inherit docstrings from the methods they override.)
        if (fdoc is not None and fuid.is_any_method() and
            not fdoc.has_docstring()):
            fdoc = self._docmap.documented_ancestor(fuid) or fdoc

        # Get the return type (if any)
        if fdoc and fdoc.returns() and fdoc.returns().type():
            rtype = self._docstring_to_html(fdoc.returns().type(),
                                            fdoc.uid(), 8)
        else: rtype = '&nbsp;'

        # Get a summary of the description (if any)
        if fdoc:
            fsum = self._summary(fdoc, fdoc.uid())
            if fsum == '&nbsp;': fsum = ''
        else:
            fsum = ''

        # Add any notable attributes to the summary.
        attribs = []
        if fuid.is_classmethod():
            attribs.append('Class method')
        if fuid.is_staticmethod():
            attribs.append('Static method')
        if inherited and self._inheritance != 'grouped':
            if fuid.cls() is None:
                href = '<i>unknown</i>'
            else:
                href = self._uid_to_href(fuid.cls(), fuid.cls().shortname())
            attribs.append('Inherited from %s' % href)
        if attribs:
            fsum += '    <i>(' + '; '.join(attribs) + ')</i>\n'

        # If we have any summary info, then put it on its own line
        # (after the function signature)
        if fsum: fsum = '<br />\n' + fsum

        # Write a row for the function.
        str = '<tr><td align="right" valign="top" width="15%">'
        str += '<font size="-1">'+rtype+'</font></td>\n  <td><code>'
        if inherited or (fdoc and fdoc.has_docstring()):
            str += self._func_signature(fname, fuid, fdoc, 1, 0, 'summary-sig')
        else:
            str += '<a name="%s"></a>' % fuid.shortname()
            str += self._func_signature(fname, fuid, fdoc, 0, 0, 'summary-sig')
        str += '</code>\n' + fsum + '</td></tr>\n'
        private.write(str)
        if fuid.is_public(): public.write(str)

    def _write_func_details(self, public, private, container, functions,
                            heading='Function Details'):
        """
        Write HTML code for a function details section.  This is used
        by L{_write_module} to describe the functions in a module; and
        by L{_write_class} to describe the methods in a class.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param container: The module or class that contains the
            functions.  C{container} is used to group the functions.
        @param functions: The functions that should be included in
            the section.
        """
        # Filter out functions that we have no docs for; these are just
        # listed in the summary table.
        docmap = self._docmap
        functions = [f for f in functions if docmap.has_key(f.target())]

        cuid = container.uid()
        if cuid.is_class():
            # Filter out inherited methods (their details docs are
            # listed under the class that defines them):
            functions = [f for f in functions
                         if f.target().cls() == cuid]

            # Filter out methods that don't define or inherit docstrings;
            # they are just listed in the summary table.
            functions = [f for f in functions
                         if docmap.documented_ancestor(f.target())]
        else:
            # Filter out functions that don't define docstrings; they
            # are just listed in the summary table.
            functions = [f for f in functions
                         if docmap[f.target()].has_docstring()]

        # If there are no functions left, then just return.
        if len(functions) == 0: return
        
        # Divide the functions into groups
        groups = container.by_group(functions)

        # Write the table header
        header = self._table_header(heading, 'details')+'</table>\n'
        _write_if_nonempty(public, private, functions, header)

        # Write an entry for each function.
        for function in functions:
            str = self._func_details_entry(function, container)
            private.write(str)
            if function.target().is_public(): public.write(str)

        # Write the table footer
        _write_if_nonempty(public, private, functions, '<br />\n\n')

    def _func_details_entry(self, function, container):
        """
        @return: The HTML code for an entry in the function details
        section.  Each entry gives a complete description of a
        documented function.
        @param function: The function that should be described by this entry.
        @type function: L{Link}
        """
        fname = function.name()
        fuid = function.target()
        # Note: by the time we get here, we know that docmap contains fuid.
        fdoc = self._docmap[fuid]

        # Get the function's signature.  Note that this needs to be
        # done before we look for an overridden ancestor.
        signature = self._func_signature(fname, fuid, fdoc)

        # Check if the function overrides anything.  Note that this
        # needs to be done before we look for an overridden ancestor.
        overrides = fdoc.overrides()
        
        # If the method doesn't define a docstring, then try to find
        # an overridden ancestor that does.  (This lets methods
        # inherit docstrings from the methods they override.)
        if fuid.is_any_method() and not fdoc.has_docstring():
            fdoc = self._docmap.documented_ancestor(fuid) or fdoc
            inherits_docs = (fdoc.uid() != fuid)
            fuid = fdoc.uid()
        else:
            inherits_docs = 0

        # Add the function's name & signature.
        str = '\n<a name="'+fuid.shortname()+'"></a>\n'
        str += '<table width="100%" class="func-details"'
        str += ' bgcolor="#e0e0e0"><tr><td>\n'
        str += '  <h3>%s\n' % signature
        if SPECIAL_METHODS.has_key(fname):
            str += '    <br /><i>'
            str += '(%s)</i>\n' % SPECIAL_METHODS[fname]
        str += '  </h3>\n'

        # Add the function's description.
        fdescr = fdoc.descr()
        if fdescr:
            str += self._docstring_to_html(fdescr, fuid, 2)
        str += '  <dl><dt></dt><dd>\n'

        # Add the parameter descriptions.  Only list parameters that
        # we have extra info for.
        fparams = fdoc.parameter_list()[:]
        str += self._parameter_list(fparams, fdoc)

        keywords = fdoc.keywords()
        if keywords:
            str += self._parameter_list(keywords, fdoc, 'Keyword Parameters')

        # Add the return value description.
        freturn = fdoc.returns()
        if freturn.descr() or freturn.type():
            str += '    <dl><dt><b>Returns:</b></dt>\n      <dd>\n'
            if freturn.descr():
                str += self._docstring_to_html(freturn.descr(), fuid, 8)
                if freturn.type():
                    rtype = self._docstring_to_html(freturn.type(), fuid, 14)
                    str += '        <br /><i>'+('&nbsp;'*10)+'\n'
                    str += ' '*8+'(type=%s)</i>\n' % rtype.strip()
            elif freturn.type():
                str += self._docstring_to_html(freturn.type(), fuid, 8)
            str += '      </dd>\n    </dl>\n'

        # Add descriptions of exceptions that can be raised.
        fraises = fdoc.raises()
        if fraises:
            str += '    <dl><dt><b>Raises:</b></dt>\n'
            for fraise in fraises:
                str += '      '
                str += '<dd><code><b>'+fraise.name()+'</b></code> -\n'
                str += self._docstring_to_html(fraise.descr(), fuid, 8)
                str +'      </dd>\n'
            str += '    </dl>\n'

        # If the function is a method that overrides another method,
        # then give a pointer to the overridden method.
        if overrides:
            str += '    <dl><dt><b>Overrides:</b></dt>\n'
            str += '      <dd>'+self._uid_to_href(overrides)
            if inherits_docs:
                str += ' <i>(inherited documentation)</i>\n'
            str += '</dd>\n    </dl>\n'

        # Add version, author, warnings, requirements, notes, etc.
        str += self._standard_fields(fdoc)
        str += '  </dd></dl>\n'
        str += '</td></tr></table>\n'
        return str

    def _parameter_list(self, parameters, container, heading='Parameters'):
        # Only describe parameters that we have something to say about.
        if not container.defines_groups():
            parameters = [p for p in parameters if p.descr() or p.type()]
        if not parameters: return ''

        # For shared descrs, only list once.
        parameters = [p for p in parameters if not p.listed_under()]

        # Get the UID of the function (for docstring output)
        fuid = container.uid()

        # Divide the parameters into groups
        groups = container.by_group(parameters)

        # Add the list header
        str = '    <dl><dt><b>%s:</b></dt>\n' % heading

        # Write entries for each group.
        for name, group in groups:
            # Add a group header (where applicable)
            if name is not None:
                str += '     <dd><dl><dt>%s</dt>\n' % name

            # Add each parameter
            for param in group:
                str += '      <dd><code><b>%s</b></code>' % param.name()
                if param.shared_descr_params():
                    for p in param.shared_descr_params():
                        str += ', <code><b>%s</b></code>' % p.name()
                if param.descr():
                    pdescr = self._docstring_to_html(param.descr(), fuid, 8)
                    str += ' -\n %s' % pdescr.rstrip()
                str += '\n'
                if param.shared_descr_params():
                    for p in [param]+param.shared_descr_params():
                        if not p.type(): continue
                        ptype = self._docstring_to_html(p.type(), fuid, 14)
                        str += ' '*8+'<br /><i>'+('&nbsp;'*10)+'\n'
                        str += (' '*8+'(type of %s=%s)</i>\n' %
                                (p.name(), ptype.strip()))
                elif param.type():
                    ptype = self._docstring_to_html(param.type(), fuid, 14)
                    str += ' '*8+'<br /><i>'+('&nbsp;'*10)+'\n'
                    str += ' '*8+'(type=%s)</i>\n' % ptype.strip()

            # Add a group footer (where applicable)
            if name is not None:
                str += '     </dd></dl>\n'

        # Add a list footer, and return.
        return str + '      </dd>\n    </dl>\n'

    def _func_signature(self, fname, fuid, fdoc, href=0, show_defaults=1,
                        css_class="sig"):
        """
        @return: The HTML code for the function signature of the
            function with the given name and documentation.
        @param fname: The short name of the function.
        @type fname: C{string}
        @param fuid: The UID of the function (used to generate the
            optional href link).
        @type fuid: L{UID}
        @param fdoc: The documentation for the function.
        @type fdoc: L{objdoc.FuncDoc}
        @param href: Whether to create an href link from the
            function's name to its details description.
        @param show_defaults: Whether or not to show default values
            for parameters.
        @param css_class: The CSS class that should be used to mark
            the signature.
        """
        # This should never happen, but just in case:
        if fdoc is None:
            return (('<span class="%s"><span class="%s-name">%s</span>'+
                     '(...)</span>') % (css_class, css_class, fname))

        # Get the function's name.
        if href: name = self._uid_to_href(fuid, fname, css_class+'-name')
        else: name = '<span class="%s-name">%s</span>' % (css_class, fname)

        # Convert the positional parameters to HTML
        params = [self._param_to_html(p, show_defaults, css_class)
                  for p in fdoc.parameters()]

        # Convert the vararg parameter to HTML
        if fdoc.vararg():
            vararg_name = fdoc.vararg().name()
            if vararg_name != '...': vararg_name = '*%s' % vararg_name
            params.append('<span class="%s-vararg">%s</span>' %
                          (css_class, vararg_name))

        # Convert the keyword parameter to HTML
        if fdoc.kwarg():
            params.append('<span class="%s-kwarg">**%s</span>' %
                          (css_class, fdoc.kwarg().name()))

        # Combine the name & the parameters.
        return ('<span class="%s">%s(%s)</span>' %
                (css_class, name, ',\n          '.join(params)))

    def _param_to_html(self, param, show_defaults, css_class):
        """
        @return: The HTML code for a single parameter.  Note that
            a single parameter can consists of a sublist of
            parameters (although this feature isn't often used).
            This is a helper function for L{_func_signature}.
        @param show_defaults: Whether or not to show default values
            for parameters.
        @param css_class: The CSS class that should be used to mark
            the signature.
        """
        if type(param) in (types.ListType, types.TupleType):
            # It's a sublist of parameters; recurse
            sublist = [self._param_to_html(p, show_defaults, css_class)
                       for p in param]
            return '('+(',\n           '.join(sublist))+')'
        else:
            # It's a single parameter; write it.
            str = ('<span class=%s-arg>%s</span>' %
                    (css_class, param.name()))
            if show_defaults and param.default() is not None:
                default = param.default()
                if len(default) > 60: default = default[:57]+'...'
                default = markup.plaintext_to_html(default)
                default = default.replace(' ', '&nbsp;')
                str += ('=<span class=%s-default>%s</span>' %
                        (css_class, default))
            return str

    #////////////////////////////////////////////////////////////
    # Property tables
    #////////////////////////////////////////////////////////////
    def _write_property_summary(self, public, private, container, properties, 
                                heading='Property Summary'):
        """
        Write HTML code for a property summary table.  This is used by
        L{_write_class} to list the properties in a class.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param container: The module or class that contains the
            properties.  C{container} is used to group the properties.
        @param properties: The properties that should be included in
            the list.
        """
        if len(properties) == 0: return

        # Divide the properties into groups
        groups = container.by_group(properties)

        # Write the table header
        header = self._table_header(heading, 'summary')
        _write_if_nonempty(public, private, properties, header)

        # Write entries for each group.  Note that every group
        # contains at least one property or inherited property.
        for name, group in groups:
            # Print a group header (except for the special group None,
            # which is always the first group).
            if name is not None:
                header = self._group_header(name)
                _write_if_nonempty(public, private, group, header)

            # Write a table row for each property.  Skip inherited
            # properties if _inheritance=='listed', since they'll be
            # listed below.
            for property in group:
                self._write_property_summary_row(public, private,
                                                 property, container)
            # Add an inheritance list, if appropriate
            if self._inheritance == 'listed':
                self._write_inheritance_list(public, private, group,
                                             container)

        # Write the table footer
        _write_if_nonempty(public, private, properties, '</table><br />\n\n')

    def _write_property_summary_row(self, public, private,
                                    property, container):
        """
        Write HTML code for a row in the property summary table.  Each
        row gives a brief description of a single property.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param property: The property that should be described by this row.
        @type property: L{Link}
        """
        puid = property.target()
        pname = property.name()
        pdoc = self._docmap.get(puid)

        # Is it an inherited property?
        cuid = container.uid()
        inherited = (cuid.is_class() and puid.cls() != cuid)

        # If it's inherited & we're listing inheritance, then don't
        # write a table row for it; it'll be included in the list.
        if inherited and self._inheritance == 'listed': return
        
        # Get the property's type (if any)
        if pdoc and pdoc.type():
            ptype = self._docstring_to_html(pdoc.type(), pdoc.uid(), 10)
            ptype = ptype.strip()
        else: ptype = '&nbsp;'

        # Get a summary of the description (if any)
        if pdoc:
            summary = self._summary(pdoc, puid)
            if summary != '&nbsp;': psum = ': '+summary
            else: psum = ''
        else: psum = ''

        # Check if we have extra info about the property.  If not,
        # then don't list it in the details section; create an anchor
        # for it instead of linking (unless we inherited it).
        link_private = inherited or (pdoc and (pdoc.fget() or pdoc.fset() or
                                               pdoc.fdel() or pdoc.descr()))
        link_public = inherited or self._property_in_public_details(puid)

        # Write a row for the property
        header = '<tr><td align="right" valign="top" '
        header += 'width="15%"><font size="-1">'+ptype+'</font></td>\n  <td>'
        private.write(header)
        if puid.is_public(): public.write(header)

        link = '<b>%s</b>' % self._link_to_html(property)
        anchor = ('<a name="%s"></a><b><code>%s</code></b>' %
                  (puid.shortname(), pname))
        if link_private: private.write(link)
        else: private.write(anchor)
        if puid.is_public():
            if link_public: public.write(link)
            else: public.write(anchor)
        str = psum
        if (self._inheritance != 'grouped' and inherit):
            cls = puid.cls()
            str += ('    <i>(Inherited from %s)</i>\n' %
                    self._uid_to_href(cls, cls.shortname()))
        str += '</td></tr>'
        private.write(str)
        if puid.is_public(): public.write(str)

    def _write_property_details(self, public, private, container, properties,
                                heading='Property Details'):
        """
        Write HTML code for a properties details section.  This is
        used by L{_write_class} to describe the properties in a class.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param container: The or class that contains the properties.
            C{container} is used to group the functions.
        @param properties: The properties that should be included in
            the section.
        """
        # Filter out properties that we have no docs for; these are
        # just listed in the summary table.
        docmap = self._docmap
        properties = [p for p in properties if docmap.has_key(p.target())]

        # Filter out inherited properties (their details docs are
        # listed under the class that defines them):
        cuid = container.uid()
        properties = [p for p in properties if p.target().cls() == cuid]

        # Filter out properties for which there's nothing more to say.
        tmp = [(p, docmap[p.target()]) for p in properties]
        properties = [p for (p,d) in tmp if
                      d.fget() or d.fset() or d.fdel() or d.descr()]

        # Check if any properties are listed on the public page.
        show_public = 0
        for p in properties:
            if self._property_in_public_details(p.target()):
                show_public = 1
                break
        
        # If there are no properties left, then just return
        if len(properties) == 0: return ''

        # Divide the properties into groups
        groups = container.by_group(properties)

        # Write the table header.
        header = self._table_header(heading, 'details')+'</table>\n'
        private.write(header)
        if show_public: public.write(header)

        # Write an entry for each property
        for property in properties:
            self._write_property_details_entry(public, private,
                                               property, container)
            
        # Write the footer
        footer = '<br />\n\n'
        private.write(footer)
        if show_public: public.write(footer)

    def _write_property_details_entry(self, public, private,
                                      property, container):
        """
        Write HTML code for an individual property in the properties
        details section.  This is used by L{_write_property_details}.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param property: The property that should be described by this entry.
        @type property: L{Link}
        """
        puid = property.target()
        pname = property.name()
        # Note: by the time we get here, we know that docmap contains puid.
        pdoc = self._docmap[puid]

        # Should we display this property in the details section for
        # the public version of the page?
        show_public = self._property_in_public_details(puid)
        
        # Add the property's name
        header = '<table width="100%" class="func-details"'
        header += ' bgcolor="#e0e0e0"><tr><td>\n'
        header += '\n<a name="%s"></a>\n' % pname
        header += '<h3>'+pname+'</h3>\n'
        private.write(header)
        if show_public: public.write(header)

        # Add the property's description (if any)
        if pdoc.descr():
            descr = self._docstring_to_html(pdoc.descr(), puid)
            private.write(descr)
            if show_public: public.write(descr)

        # Add the property's accessor methods
        if pdoc.fget() or pdoc.fset() or pdoc.fdel():
            header2 = '<dl>\n  <dt></dt>\n  <dd>\n    <dl>\n'
            private.write(header2)
            if show_public: public.write(header2)
            for (fuid,name) in [(pdoc.fget(), 'Get'),
                                (pdoc.fset(), 'Set'),
                                (pdoc.fdel(), 'Delete')]:
                if fuid:
                    str = '      <dt><b>%s Method:' % name
                    str += '</b></dt>\n      <dd>'
                    if fuid.is_routine():
                        fdoc = self._docmap.get(fuid)
                        str += self._func_signature(fuid.shortname(), fuid,
                                                    fdoc, 1, 0, 'summary-sig')
                    elif self._docmap.has_key(fuid):
                        str += self._uid_to_href(fuid)
                    else:
                        var = Var(fuid.name(), fuid)
                        linelen = self._propfunc_linelen
                        str += self._pprint_var_value(var, 0, linelen)
                    str += '\n      </dd>\n'
                    private.write(str)
                    if fuid.is_public(): public.write(str)
            footer2 = '    </dl>\n  </dd>\n</dl>'
            private.write(footer2)
            if show_public: public.write(footer2)

        # Add the footer.
        footer = '</td></tr></table>'
        private.write(footer)
        if show_public: public.write(footer)
    
    def _property_in_public_details(self, puid):
        """
        @return: True if the given property should be shown in the
        public version of the property details section.
        """
        if not (puid and puid.is_public()): return 0
        pdoc = self._docmap.get(puid)
        if not pdoc: return 0
        return ((pdoc.fget() and pdoc.fget().is_public()) or
                (pdoc.fset() and pdoc.fset().is_public()) or
                (pdoc.fdel() and pdoc.fdel().is_public()) or
                pdoc.descr())

    #////////////////////////////////////////////////////////////
    # Variable tables
    #////////////////////////////////////////////////////////////
    
    def _write_var_summary(self, public, private, container, variables, 
                           heading='Variable Summary'):
        """
        Write HTML code for a variable summary table.  This is used by
        L{_write_module} to list the variables in a module; and by
        L{_write_class} to list the instance and class variables in a
        class.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param container: The module or class that contains the
            variables.  C{container} is used to group the properties.
        @param variables: The variables that should be included in
            the list.
        """
        if len(variables) == 0: return

        # Divide the variables into groups
        groups = container.by_group(variables)

        # Write the table header
        header = self._table_header(heading, 'summary')
        _write_if_nonempty(public, private, variables, header)

        # Write entries for each group.  Note that every group
        # contains at least one variable or inherited variable.
        for name, group in groups:
            # Print a group header (except for the special group None,
            # which is always the first group).
            if name is not None:
                header = self._group_header(name)
                _write_if_nonempty(public, private, group, header)

            # Write a table row for each property.  Skip inherited
            # properties if _inheritance=='listed', since they'll be
            # listed below.
            for var in group:
                self._write_var_summary_row(public, private, var, container)

            # Add an inheritance list, if appropriate
            if (self._inheritance == 'listed' and
                isinstance(container, ClassDoc)):
                self._write_inheritance_list(public, private,
                                             group, container)
                                             
        # Write the table footer
        _write_if_nonempty(public, private, variables, '</table><br />\n\n')

    def _write_var_summary_row(self, public, private, var, container):
        """
        Write HTML code for a row in the variable summary table.  Each
        row gives a brief description of a single variable.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param var: The variable that should be described by this row.
        """
        vuid = var.uid()
        vname = var.name()

        # Is it an inherited variable?
        cuid = container.uid()
        inherited = (cuid.is_class() and vuid.cls() != cuid
                     and vuid.is_variable())

        # If it's inherited & we're listing inheritance, then don't
        # write a table row for it; it'll be included in the list.
        if inherited and self._inheritance == 'listed': return
        
        # Add the variable's type (if any)
        if var.type():
            vtype = self._docstring_to_html(var.type(), var.uid(), 10).strip()
        else: vtype = '&nbsp;'
        str = '<tr><td align="right" valign="top" '
        str += 'width="15%"><font size="-1">'+vtype+'</font></td>\n'

        # Special case: if it's a module, class, type, or func, then
        # point to its real value.
        if not vuid.is_variable():
            str += ('  <td><a name="'+vname+ '"></a><b><code>'+vname+
                   '</code></b> = '+self._uid_to_href(vuid)+'</td></tr>\n')
            private.write(str)
            if vuid.is_public(): public.write(str)
            return

        # Add the variable's name.  If we don't have any extra
        # info about it, then just list it in the summary (not the
        # details).
        str += '<td><b>'
        if not (var.descr() or var.type() or var.has_value()):
            str += '<code><a name="%s">%s</a></code>' % (vname, vname)
        else:
            str += self._uid_to_href(var.uid(), vname)

        # Add the variable's description
        vname = var.name()
        if var.descr():
            vsum = ': ' +self._summary(var.descr(), var.uid())
        else:
            title = self._var_value_tooltip(var)
            linelen = self._variable_summary_linelen - len(vname) - 2
            val = self._pprint_var_value(var, 0, linelen)
            vsum = ' = <span title="%s">%s</span>' % (title, val)
        str += '</b>' + vsum
        if (self._inheritance != 'grouped' and inherited):
            str += ('    <i>(Inherited from %s)</i>\n' %
                    self._uid_to_href(cuid, cuid.shortname()))
            
        str += '</td></tr>\n'
        private.write(str)
        if vuid.is_public(): public.write(str)

    def _write_var_details(self, public, private, container, vars,
                           heading='Variable Details'):
        """
        Write HTML code for a variable details section.  This is used
        by L{_write_module} to describe the variables in a module; and
        by L{_write_class} to describe the instance and class
        variables in a class.
        @param public: The output stream for the public version of the page.
        @param private: The output stream for the private version of the page.
        @param container: The module or class that contains the
            variables.  C{container} is used to group the properties.
        @param vars: The variables that should be included in
            the section.
        """
        # Filter out variables that we have no extra info for; these
        # are just listed in the summary table.  Also, filter out docs
        # for modules/funcs/classes/properties (we point to these
        # from the summary)
        vars = [v for v in vars if v.uid().is_variable() and
                (v.descr() or v.type() or v.has_value())]

        # Filter out inherited variables (their details docs are
        # listed under the class that defines them) and
        cuid = container.uid()
        if cuid.is_class():
            vars = [v for v in vars if v.uid().cls() == cuid]
        
        # Write the header
        if len(vars) == 0: return ''
        str = self._table_header(heading, 'details')+'</table>\n'
        _write_if_nonempty(public, private, vars, str)

        # Add entries for each variable
        for var in vars:
            str = self._var_details_entry(var, container)
            if str:
                private.write(str)
                if var.uid().is_public(): public.write(str)
            
        # Write the footer
        _write_if_nonempty(public, private, vars, '<br />\n\n')

    def _var_details_entry(self, var, container):
        """
        @return: The HTML code for an entry in the variable details
        section.  Each entry gives a complete description of a
        documented variable.
        @param var: The variable that should be described.
        """
        vname = var.name()
        vuid = var.uid()

        # Add a header with the variable name.
        str = ('<table width="100%" class="var-details"'+
                ' bgcolor="#e0e0e0">'+
                '<tr><td>\n')
        str += '<a name="'+vname+'"></a>\n'
        str += '<h3>'+vname+'</h3>\n'

        # Add the variable's description (if any)
        if var.descr():
            str += self._docstring_to_html(var.descr(), vuid)

        # Add a header for the type and/or value
        vtyp = var.type()
        hasval = var.has_value()
        if vtyp is not None or hasval:
            str += '<dl>\n  <dt></dt>\n  <dd>\n    <dl>\n'

            # Add the variable's type (if any)
            if vtyp:
                str += '      <dt><b>Type:</b></dt>\n      <dd>\n' 
                str += self._docstring_to_html(vtyp, vuid)
                str += '\n      </dd>\n'
    
            # Add the variable's value (if any)
            if hasval:
                str += '<span title="%s">' % self._var_value_tooltip(var)
                str += '      <dt><b>Value:</b></dt>\n' 
                str += '      <dd><table><tr><td>\n<pre class="variable">\n'
                str += self._pprint_var_value(var)
                str += '</pre>\n        </td></tr></table></dd>\n'
                str += '</span>'

            # Add a footer for the type and/or value
            str += '    </dl>\n  </dd>\n</dl>'

        # Add the entry footer, and return the completed string.
        return str + '</td></tr></table>\n'

    def _var_value_tooltip(self, var):
        """
        @return: A string representation of the value of the variable
            C{var}, suitable for use in a tooltip.  In particular, use
            C{repr} to convert the var to a string; truncate it to
            L{_variable_tooltip_linelen}; and convert it to HTML.
        @type var: L{Var}
        """
        try: val = repr(var.uid().value())
        except: val = '...'
        if len(val) > self._variable_tooltip_linelen:
            val = val[:self._variable_tooltip_linelen-3]+'...'
        val = val.replace('&', '&amp;').replace('"', '&quot;')
        val = val.replace('<', '&lt;').replace('>', '&gt;')
        return val

    def _pprint_var_value(self, var, multiline=1, summary_linelen=0):
        """
        @return: A string representation of the value of the variable
            C{var}, suitable for use in a C{<pre>...</pre>} HTML
            block.
        @type var: L{Var}
        """
        if not var.has_value(): return ''
        val = var.uid().value()

        # Handle the most common cases first.  The following types
        # will never need any line wrapping, etc; and they cover most
        # variable values (esp int, for constants).  I leave out
        # LongType on purpose, since long values may need line-
        # wrapping.
        if (type(val) is types.IntType or type(val) is types.FloatType or
            type(val) is types.NoneType or type(val) is types.ComplexType):
            # none of these should contain '&', '<' or '>'.
            vstr = repr(val)
            return vstr + '&nbsp;' * (self._variable_linelen-len(vstr))

        # For strings, use repr.  Use tripple-quoted-strings where
        # appropriate.
        elif type(val) is types.StringType:
            vstr = repr(val)
            if vstr.find(r'\n') >= 0 and multiline:
                body = vstr[1:-1].replace(r'\n', '\n')
                vstr = ('<span class="variable-quote">'+vstr[0]*3+'</span>'+
                        markup.plaintext_to_html(body) +
                       '<span class="variable-quote">'+vstr[0]*3+'</span>')
                     
            else:
                vstr = ('<span class="variable-quote">'+vstr[0]+'</span>'+
                        markup.plaintext_to_html(vstr[1:-1])+
                       '<span class="variable-quote">'+vstr[0]+'</span>')

        # For lists, tuples, and dicts, use pprint.  When possible,
        # restrict the amount of stuff that pprint needs to look at,
        # since pprint is surprisingly slow.
        elif type(val) is types.TupleType or type(val) is types.ListType:
            try: vstr = repr(val)
            except: vstr = '...'
            if multiline and len(vstr) > self._variable_linelen:
                vstr = pprint.pformat(val[:self._variable_maxlines+1])
            vstr = markup.plaintext_to_html(vstr)
        elif type(val) is type({}):
            try: vstr = repr(val)
            except: vstr = '...'
            if multiline and len(vstr) > self._variable_linelen:
                if len(val) < self._variable_maxlines+50:
                    vstr = pprint.pformat(val)
                else:
                    shortval = {}
                    for (k,v) in val.items()[:self._variable_maxlines+1]:
                        shortval[k]=v
                    vstr = pprint.pformat(shortval)
            vstr = markup.plaintext_to_html(vstr)

        # For regexps, use colorize_re.
        elif type(val).__name__ == 'SRE_Pattern':
            vstr = colorize_re(val)
           
        # For other objects, use repr to generate a representation.
        else:
            try: vstr = markup.plaintext_to_html(repr(val))
            except: vstr = '...'

        # For the summary table, just return the value; don't
        # bother to word-wrap.
        if not multiline:
            vstr = vstr.replace('\n', '')
            # Change spaces to &nbsp; (except spaces in html tags)
            vstr = vstr.replace(' ', '&nbsp;')
            vstr = vstr.replace('<span&nbsp;', '<span ')
            vstr = self._linewrap_html(vstr, summary_linelen, 1)
            return '<code>%s</code>\n' % vstr

        # Do line-wrapping.
        return self._linewrap_html(vstr, self._variable_linelen,
                                   self._variable_maxlines)

    def _linewrap_html(self, str, linelen, maxlines):
        """
        Add line-wrapping to the HTML string C{str}.  Line length is
        determined by C{linelen}; and the maximum number of
        lines to display is determined by C{maxlines}.  This
        function treats HTML entities (e.g., C{&amp;}) as single
        characters; and ignores HTML tags (e.g., C{<p>}).
        """
        LINEWRAP_MARKER = r'<span class="variable-linewrap">\</span>'
        ELLIPSIS_MARKER = r'<span class="variable-ellipsis">...</span>'
        
        lines = []
        start = end = cnum = 0
        while len(lines) <= maxlines and end < len(str):
            # Skip over HTML tags.
            if str[end] == '<':
                end = str.find('>', end)
                cnum -= 1

            # HTML entities just count as 1 char.
            elif str[end] == '&':
                end = str.find(';', end)

            # Go on to the next character.
            cnum += 1
            end += 1

            # Check for end-of-line.
            if str[end-1] == '\n':
                lines.append(str[start:end-1])
                cnum = 0
                start = end

            # Check for line-wrap
            if cnum == linelen and end<len(str) and str[end] != '\n':
                if maxlines == 1:
                    return str[start:end]+ELLIPSIS_MARKER
                lines.append(str[start:end]+LINEWRAP_MARKER)
                cnum = 0
                start = end

        # Add on anything that's left.
        if end == len(str):
            lines.append(str[start:end])

        # Use the ellipsis marker if the string is too long.
        if len(lines) > maxlines:
            lines[-1] = ELLIPSIS_MARKER
            cnum = 3

        # Pad the last line to linelen.
        lines[-1] += ' '*(linelen-cnum+1)

        return ('\n').join(lines)
            
    #////////////////////////////////////////////////////////////
    # Term index generation
    #////////////////////////////////////////////////////////////
    
    def _get_index_terms(self, parsed_docstring, link, terms, links):
        """
        A helper function for L{_extract_term_index}.
        
        For each index term M{t} with key M{k} in C{parsed_docstring},
        modify C{terms} and C{links} as follows:
          - Set C{terms[M{k}] = t} (if C{terms[M{k}]} doesn't exist).
          - Append C{link} to C{links[M{k}]}.
        """
        if parsed_docstring is None: return
        for term in parsed_docstring.index_terms():
            key = self._term_index_to_anchor(term)
            if not terms.has_key(key):
                terms[key] = term
                links[key] = []
            links[key].append(link)

    def _extract_term_index(self):
        """
        Extract the set of terms that should be indexed from all
        documented docstrings.  Return the extracted set as a
        list of tuples of the form C{(key, term, [links])}.
        This list is used by L{_write_indices} to construct the
        term index.
        @rtype: C{list} of C{(string, L{markup.ParsedDocstring},
                              list of Link)}
        """
        terms = {}
        links = {}
        for (uid, doc) in self._docmap.items():
            if uid.is_function():
                if uid.module() is None: continue # ouch.
                link = Link(uid.name(), uid.module())
            elif uid.is_method():
                if uid.cls() is None: continue # ouch.
                link = Link(uid.name(), uid.cls())
            else:
                link = Link(uid.name(), uid)

            # Get index items from standard fields.
            for field in self._standard_field_values(doc):
                self._get_index_terms(field, link, terms, links)
            if doc.descr():
                self._get_index_terms(doc.descr(), link, terms, links)

            # Get index items from object-specific fields.
            if isinstance(doc, ModuleDoc):
                for var in doc.variables():
                    self._get_index_terms(var.descr(), link, terms, links)
                    self._get_index_terms(var.type(), link, terms, links)
            elif isinstance(doc, ClassDoc):
                for var in doc.ivariables() + doc.cvariables():
                    self._get_index_terms(var.descr(), link, terms, links)
                    self._get_index_terms(var.type(), link, terms, links)
            elif isinstance(doc, FuncDoc):
                params = doc.parameter_list()
                if doc.returns(): params.append(doc.returns())
                for param in params:
                    self._get_index_terms(param.descr(), link, terms, links)
                    self._get_index_terms(param.type(), link, terms, links)
                for fraise in doc.raises():
                    self._get_index_terms(fraise.descr(), link, terms, links)

        # Combine terms & links into one list
        keys = terms.keys()
        keys.sort()
        return [(k, terms[k], links[k]) for k in keys]

    #////////////////////////////////////////////////////////////
    # Identifier index generation
    #////////////////////////////////////////////////////////////

    def _extract_identifier_index(self):
        """
        @return: A list of the C{UID}s of all objects and variables
            documented by C{_docmap}.  This list is used by
            L{_write_indices} to construct the identifier index.
        @rtype: C{list} of L{UID}
        """
        # List of (sort-key, UID), where sort-key is
        # uid.shortname().lower().
        decorated_uids = []

        for (uid, doc) in self._docmap.items():
            decorated_uids.append( (uid.shortname().lower(), uid) )
            if uid.is_module():
                decorated_uids += [(v.name().lower(), v.uid())
                                   for v in doc.variables()]
            elif uid.is_class():
                decorated_uids += [(v.name().lower(), v.uid())
                                   for v in doc.ivariables() + doc.cvariables()]

        decorated_uids.sort()        
        return [u[1] for u in decorated_uids]

    #////////////////////////////////////////////////////////////
    # Table of contents (frames) generation
    #////////////////////////////////////////////////////////////

    def _write_toc_section(self, public, private, section, links):
        if not links: return

        # Write a header
        str = self._start_of(section)
        str += ('<font size="+1"><b>%s</b></font><br />\n' %
                section.replace(' ', '&nbsp;'))
        public.write(str); private.write(str)

        # Sort the links by name
        decorated = [(link.name().lower(), link) for link in links]
        decorated.sort()
        links = [d[-1] for d in decorated]

        # Write all the links.
        for link in links:
            str = ('<a target="mainFrame" href="%s">%s</a><br />\n' %
                   (self._uid_to_uri(link.target()), link.name()))
            private.write(str)
            if link.is_public(): public.write(str)

        # Write a footer
        str = '<br />\n'
        public.write(str); private.write(str)

    #////////////////////////////////////////////////////////////
    # Docstring -> HTML Conversion
    #////////////////////////////////////////////////////////////

    def _term_index_to_anchor(self, term):
        """
        Given the name of an inline index item, construct a URI anchor.
        These anchors are used to create links from the index page to each
        index item.
        """
        # Include "-" so we don't accidentally collide with the name
        # of a python identifier.
        s = re.sub(r'\s\s+', '-', term.to_plaintext(None))
        return "index-"+re.sub("[^a-zA-Z0-9]", "_", s)

    def _docstring_to_html(self, docstring, uid, indent=0):
        """
        @return: A string containing the HTML encoding for the given
            C{ParsedDocstring}
        @rtype: L{markup.ParsedDocstring}
        @type uid: C{None} or C{UID}
        """
        assert uid is None or isinstance(uid, UID)
        if docstring is None: return ''
        linker = _HTMLDocstringLinker(self, uid)
        return docstring.to_html(linker, indent=indent)

    #////////////////////////////////////////////////////////////
    # Standard fields
    #////////////////////////////////////////////////////////////

    def _standard_field_values(self, doc):
        """
        @return: A list of epytext field values that includes all
        fields that are common to all L{ObjDoc}s.
        C{_standard_field_values} is used by L{_extract_term_index}.
        @rtype: C{list} of L{markup.ParsedDocstring}
        """
        field_values = []
        for field in doc.fields():
            field_values += doc.field_values(field)
        return field_values

    def _standard_fields(self, doc):
        """
        @return: HTML code containing descriptions of the epytext
        fields that are common to all L{ObjDoc}s (except for C{descr}).
        @rtype: C{string}
        @param doc: The object whose fields should be described.
        """
        uid = doc.uid()
        if uid.is_module() or uid.is_class(): container = uid
        else: container = uid.cls() or uid.module()
        str = ''

        for field in doc.fields():
            values = doc.field_values(field)
            if not values: continue
            items = [self._docstring_to_html(v, uid) for v in values]
            str += self._descrlist(items, field.singular,
                                   field.plural, field.short)

        return str
            
    def _descrlist(self, items, singular, plural=None, short=0):
        """
        @return: The HTML code for a list of description items.
        @param items: The description items.
        @type items: C{list} of C{string}
        @param singular: The name of the list, if there is one
            element. 
        @param plural: The name of the list, if there are multiple
            elements.  
        """
        if plural is None: plural = singular
        if len(items) == 0: return ''
        if len(items) == 1 and singular and singular is not None:
            return '<p><b>%s:</b> %s</p>\n\n' % (singular, items[0])
            #return '<p><b>%s:</b> %s<br /></p>\n\n' % (singular, items[0])
        if short:
            str = '<dl><dt><b>%s:</b></dt>\n  <dd>\n    ' % plural
            items = [item.strip() for item in items]
            return str + ',\n    '.join(items) + '\n  </dd>\n</dl>\n\n'
        else:
            str = '<p><b>%s:</b>\n<ul>\n  <li>' % plural
            return (str + '</li>\n  <li>'.join(items) +
                    '\n  </li>\n</ul></p>\n\n')
    
    #////////////////////////////////////////////////////////////
    # Helper functions
    #////////////////////////////////////////////////////////////

    def _find_top_page(self, pagename):
        """
        Find the top page for the API documentation.  This page is
        used as the default page shown in the main frame, when frames
        are used.  When frames are not used, a redirect page is
        created from C{index.html} to the top page.

        @param pagename: The name of the page, as specified by the
            keyword argument C{top} to the constructor.
        @type pagename: C{string}
        @return: The URL of the top page.
        @rtype: C{string}
        """
        # If the top_page is unspecified, find an appropriate page.
        if not pagename:
            top = self._find_toplevel()
            if top: return self._uid_to_uri(top)
            else: return 'trees.html'

        # If it's a URL, then use it directly.
        if pagename.startswith('http:'):
            return pagename

        # Look for it in the docmap.
        for uid in self._docmap.keys():
            if pagename == uid.name():
                if uid.is_module() or uid.is_class():
                    return self._uid_to_uri(uid)
                else:
                    estr = ('Warning: Specified top page object %s' % uid
                            + ' is not a module\n         or a class.')
                    if sys.stderr.softspace: print >>sys.stderr
                    print >>sys.stderr, estr
                    return self._find_top_page(None)
                
        # Try importing it as a module.
        try:
            uid = make_uid(import_module(pagename))
            if self._docmap.has_key(uid):
                return self._uid_to_uri(uid)
            else:
                estr = ('Warning: Specified top page object %s' % uid
                        + ' is not documented.')
                if sys.stderr.softspace: print >>sys.stderr
                print >>sys.stderr, estr
                return self._find_top_page(None)
        except: pass

        # Is it a special page name?  Note that these could, in
        # principle, be shadowed by modules or classes named "html"
        # contained in packages named "index" etc.
        if pagename in ('indices.html', 'help.html',
                        'trees.html'):
            return pagename

        # We didn't find anything.
        estr = ('Warning: Unable to find the specified top_page %s.'
                % pagename)
        if sys.stderr.softspace: print >>sys.stderr
        print >>sys.stderr, estr
        return self._find_top_page(None)
    
    def _find_toplevel(self):
        """
        @return: The UID of the top-level module or package, or

            C{None} if there is no top-level module or package.
        @rtype: L{UID} or C{None}
        """
        modules = []
        packages = []
        for (uid, doc) in self._docmap.items():
            if not isinstance(doc, ModuleDoc): continue
            modules.append(uid)
            if doc.ispackage():
                packages.append(uid)

        # Is there a unique module?
        if len(packages) == 0 and len(modules) == 1:
            return modules[0]
        
        # Is there a unique (top-level) package?
        if len(packages) == 0: return None
        else:
            for pkg in packages:
                for pkg2 in packages:
                    if pkg is pkg2: continue
                    if not pkg2.descendant_of(pkg): break
                else: return pkg

        # There is no top-level object.
        return None

    def _header(self, name):
        """
        @return: The HTML code for the header of a page with the given
            name.
        @rtype: C{string}
        """
        return HEADER % name
               
    def _footer(self):
        """
        @return: The HTML code for the footer of a page.
        @rtype: C{string}
        """
        timestamp = time.asctime(time.localtime(time.time()))
        return FOOTER % (epydoc.__version__, timestamp)

    def _summary(self, doc, uid, indent=0):
        """
        @return: The HTML code for the summary description of the
            object documented by C{doc}.  A summary description is the
            first sentence of the C{doc}'s 'description' field.  If the
            C{doc} has no 'description' field, but does have a
            'return' field, then the summary is taken from the return
            field instead.
        @rtype: C{string}
        @param doc: The documentation for the object whose summary
            should be returned.
        @type doc: L{objdoc.ObjDoc}
        @type uid: L{uid.UID}
        """
        summary = self._docstring_to_html(doc.summary(), uid, indent).strip()
        if not summary:
            if (isinstance(doc, FuncDoc) and
                doc.returns().descr() is not None):
                summary = doc.returns().descr().summary()
                summary = self._docstring_to_html(summary, uid).strip()
                return 'Return '+summary[:1].lower() + summary[1:]
            else:
                return '&nbsp;'
        # Hack: ignore CVS $Id: html.py,v 1.82 2004/03/19 19:03:18 edloper Exp $ tags.
        summary = re.sub(r'\$[Ii][Dd]:[^\$]*\$', '', summary).strip()
        return summary or '&nbsp;'

    def _write_imports(self, public, private, doc):
        modules = doc.imported_modules()
        all_classes = doc.imported_classes()
        functions = doc.imported_functions()
        variables = doc.imported_variables()
        (classes,excepts) = self._split_classes(all_classes)

        header = self._start_of('Imports')+'<dl>\n'
        everything=modules+all_classes+functions+variables
        _write_if_nonempty(public, private, everything, header)

        # Use full UID names for modules; and short (link) names for
        # everything else.
        if len(modules) > 0:
            header = '  <dt><b>Imported modules:</b></dt>\n  <dd>\n    '
            _write_if_nonempty(public, private, modules, header)
            public_hrefs, private_hrefs = [], []
            for m in modules:
                str = self._uid_to_href(m.target())
                if m.name() != m.target().shortname():
                    str += ' (as <code>%s</code>)' % m.name()
                private_hrefs.append(str)
                if m.target().is_public(): public_hrefs.append(str)
            private.write(',\n    '.join(private_hrefs) + '\n  </dd>\n')
            public.write(',\n    '.join(public_hrefs) + '\n  </dd>\n')
        if len(classes) > 0:
            header = '  <dt><b>Imported classes:</b></dt>\n  <dd>\n    '
            _write_if_nonempty(public, private, classes, header)
            hrefs = [(c, self._link_to_html(c)) for c in classes]
            private_hrefs = [href for (c, href) in hrefs]
            public_hrefs = [href for (c, href) in hrefs if c.is_public()]
            private.write(',\n    '.join(private_hrefs) + '\n  </dd>\n')
            public.write(',\n    '.join(public_hrefs) + '\n  </dd>\n')
        if len(excepts) > 0:
            header = '  <dt><b>Imported exceptions:</b></dt>\n  <dd>\n    '
            _write_if_nonempty(public, private, excepts, header)
            hrefs = [(e, self._link_to_html(e)) for e in excepts]
            private_hrefs = [href for (e, href) in hrefs]
            public_hrefs = [href for (e, href) in hrefs if e.is_public()]
            private.write(',\n    '.join(private_hrefs) + '\n  </dd>\n')
            public.write(',\n    '.join(public_hrefs) + '\n  </dd>\n')
        if len(functions) > 0:
            header = '  <dt><b>Imported functions:</b></dt>\n  <dd>\n    '
            _write_if_nonempty(public, private, functions, header)
            hrefs = [(f, self._link_to_html(f)) for f in functions]
            private_hrefs = [href for (f, href) in hrefs]
            public_hrefs = [href for (f, href) in hrefs if f.is_public()]
            private.write(',\n    '.join(private_hrefs) + '\n  </dd>\n')
            public.write(',\n    '.join(public_hrefs) + '\n  </dd>\n')
        if len(variables) > 0:
            header = '  <dt><b>Imported variables:</b></dt>\n  <dd>\n    '
            _write_if_nonempty(public, private, variables, header)
            private_names = ['<code>%s</code>' % v.name()
                             for v in variables]
            public_names = ['<code>%s</code>' % v.name()
                            for v in variables if v.is_public()]
            private.write(',\n    '.join(private_names) + '\n  </dd>\n')
            public.write(',\n    '.join(public_names) + '\n  </dd>\n')
            
        _write_if_nonempty(public, private, everything, '</dl>\n\n')
                                
    def _link_to_html(self, link):
        """
        @return: The HTML code for the given link.  This code consists
            of an anchor with an href to the page for the link's
            target, and with text taken from the link's name. If the
            target is not documented, then the HTML code will just
            contain the name, and no href.
        @rtype: C{string}
        @type link: L{uid.Link}
        """
        return self._uid_to_href(link.target(), link.name())

    def _uid_to_filename(self, uid):
        # Make sure this is consistant w/ _uid_to_uri.
        if uid.is_module():
            return '%s-module.html' % uid.name()
        elif uid.is_class():
            return '%s-class.html' % uid.name()
        else:
            raise AssertionError, 'Bad UID type: %r' % uid

    def _uid_to_uri(self, uid):
        """
        @return: a URI that points to the description of the object
            identified by C{uid}.
        @rtype: C{string}
        @param uid: A unique identifier for the object.
        @type uid: L{UID}
        """
        # Cache the URI, since we re-use these a lot.  (This is a
        # slight hack, but profiling shows that it's worth it.)
        if hasattr(uid, '_uri'): return uid._uri
        if uid.is_module():
            uid._uri = '%s-module.html' % uid.name()
        elif uid.is_class():
            uid._uri = '%s-class.html' % uid.name()
        else:
            parent = uid.parent()
            if parent is None:
                uid._uri = '%s-unknown.html' % uid.name() # Error
            elif parent.is_module():
                uid._uri = '%s-module.html#%s' % (parent.name(),
                                                  uid.shortname())
            elif parent.is_class():
                uid._uri = '%s-class.html#%s' % (parent.name(),
                                                 uid.shortname())
            else:
                uid._uri = '%s-unknown.html' % uid.name() # Error

        # For private UIDs, explicitly link to the private docs.
        if uid.is_private() and self._create_private_docs:
            uid._uri = os.path.join('..', 'private', uid._uri)
            
        return uid._uri
            
    def _documented(self, uid):
        """
        @return: True if the given UID is documented by the
            documentation map for this C{HTMLFormatter}.
        @rtype: C{boolean}
        """
        # Does it have a UID?
        if uid is None:
            return 0

        # Is it private, if we're not showing private?
        if (not self._create_private_docs) and uid.is_private():
            return 0

        # Is it a variable or routine whose parent is not documented?
        if ((uid.is_routine() or uid.is_variable()) and
            (not self._docmap.has_key(uid.parent()))):
            return 0

        # Is it a non-variable that's not documented? (variables are
        # not included in the docmap)
        if (not uid.is_variable() and not self._docmap.has_key(uid)):
            return 0

        # Otherwise, it must be documented.
        return 1
    
    def _uid_to_href(self, uid, label=None, css_class=None, code=1):
        """
        @return: The HTML code to link to the given UID.  This code
            consists of an anchor with an href to the page for C{uid}.
            If C{label} is not C{None}, then it is used as the text
            for the link; otherwise, C{uid} is used as the text.  If
            C{uid} is not documented, then the HTML code will just
            contain the name, and no href.
        @rtype: C{string}
        @type uid: L{uid.UID}
        @type label: C{string}
        @param code: Whether or not to include C{<code>...</code>}
            tags around the label.
        """
        # We shouldn't need this, but include it just in case.
        if uid is None and label is None:
            return '<code>??</code>'
        
        # Find a default value for the label.
        if label is None: label = uid.name()

        if not self._documented(uid):
            if code: return '<code>%s</code>' % label
            else: return '%s' % label

        # Construct an href, using uid_to_uri.
        if css_class and code:
            return ('<a href="%s" class="%s"><code>%s</code></a>' %
                    (self._uid_to_uri(uid), css_class, label))
        elif css_class:
            return ('<a href="%s" class="%s">%s</a>' %
                    (self._uid_to_uri(uid), css_class, label))
        elif code:
            return ('<a href="%s"><code>%s</code></a>' %
                    (self._uid_to_uri(uid), label))
        else:
            return ('<a href="%s">%s</a>' %
                    (self._uid_to_uri(uid), label))

    def _start_of(self, heading):
        """
        @return: The HTML code for a 'start-of' comment.  These
            comments are used to deliniate sections of the HTML
            output.
        @rtype: C{string}
        @param heading: The name of the section that is starting.
        @type heading: C{string}
        """
        return '\n<!-- =========== START OF '+heading.upper()+\
               ' =========== -->\n'
    
    def _group_header(self, group):
        group = re.sub('&', '&amp;', group)
        group = re.sub('<', '&lt;', group)
        return '<tr bgcolor="#e8f0f8" class="group">\n'+\
               '  <th colspan="2">' + '&nbsp;'*4 + group + '</th></tr>\n'
    
    def _table_header(self, heading, css_class):
        """
        @return: The HTML code for the start of a table.  This is used
            by class tables, function tables, variable tables, etc.
        @rtype: C{string}
        @param heading: The name for the table.
        @type heading: C{string}
        @param css_class: The css class for the table.  This is used
            to allow different tables to be given different styles.
            Currently, the following classes are used: C{'summary'};
            C{'details'}; and C{'index'}.
        """
        return self._start_of(heading)+\
               '<table class="'+css_class+'" border="1" cellpadding="3"' +\
               ' cellspacing="0" width="100%" bgcolor="white">\n' +\
               '<tr bgcolor="#70b0f0" class="'+css_class+'">\n'+\
               '  <th colspan="2">' + heading + '</th></tr>\n'
    
    def _split_classes(self, classes_and_excepts):
        """
        Divide the classes fromt the given module into exceptions and
        non-exceptions.  This is used by L{_write_module} to list
        exceptions and non-exceptions separately.

        @param classes_and_excepts: The list of classes to split up.
        @type classes_and_excepts: C{list} of L{uid.Link}
        @return: A list C{(I{classes}, I{excepts})}, where
            C{I{classes}} is the list of non-exception classes, and
            C{I{excepts}} is the list of exception classes.
        @rtype: C{pair} of C{list} of L{uid.Link}
        """
        classes = []
        excepts = []
        for link in classes_and_excepts:
            try:
                if (self._docmap.has_key(link.target()) and
                    self._docmap[link.target()].is_exception()):
                    excepts.append(link)
                else:
                    classes.append(link)
            except:
                classes.append(link)
        return (classes, excepts)
        
    def _write_inheritance_list(self, public, private, links, cls):
        """
        @return: A string containing HTML that lists all objects from
            that were inherited from a base ancestor of C{cls}.
            Only the objects linked to from one of the links in
            C{links} are considered.  The HTML lists the objects in
            one row of a table, grouped by ancestor.
        @type links: C{list} of L{Link}
        @param links: The set of member objects of C{cls}
            that should be listed by ancestor.
        @type cls: L{UID}
        @param cls: The UID of the class whose inherited objects
            should be listed.
        """
        cuid = cls.uid()
        # Group the objects by defining class
        inh_dict = {}
        for link in links:
            if isinstance(link, Link): key = link.target().cls()
            else: key = link.uid().cls()
            if key == cuid: continue
            if key is None: continue
            if not inh_dict.has_key(key): inh_dict[key] = []
            inh_dict[key].append(link)

        if not inh_dict: return

        # Write a header
        header = '  <tr><td colspan="2">\n'
        _write_if_nonempty(public, private, links, header)

        # Get the inheritance items, and sort them in the container
        # class's base order.
        inh_items = []
        for base in cls.base_order():
            if inh_dict.has_key(base):
                inh_items.append((base, inh_dict[base]))
                del inh_dict[base]
        inh_items += inh_dict.items() # Should be unnecessary

        for j in range(len(inh_items)):
            (base, obj_links) = inh_items[j]
            header = ('    <b>Inherited from %s:</b>' %
                      self._uid_to_href(base, base.shortname()))
            _write_if_nonempty(public, private, obj_links, header)

            private_str = ''
            public_str = ''
            for link in obj_links:
                if isinstance(link, Link): str = self._link_to_html(link)
                else: str = self._uid_to_href(link.uid(), link.name())
                str = '\n      %s, ' % str
                private_str += str
                if link.is_public(): public_str += str
            private.write(private_str[:-2])
            public.write(public_str[:-2])
                
            if j != len(inh_items)-1:
                str = '\n      <br />\n'
                _write_if_nonempty(public, private, obj_links, str)

        footer = '\n    </td></tr>\n'
        _write_if_nonempty(public, private, links, footer)

    #////////////////////////////////////////////////////////////
    # Public string generation functions
    #////////////////////////////////////////////////////////////

    def format(object, error_stream=None, show_private=True, 
               body_only=True, **options): # [staticmethod]
        """
        @return: A string containing the HTML documentation for the
            given object.
        @param object: The object to document.  C{object} can be
            a module, a class, a function, a method, or a property;
            or it can be the L{UID} of any object; or it can be
            a L{Link} to any object.
        @param error_stream: A file or stream where errors and
            warnings will be written.  If C{error_stream} is
            unspecified, then warnings and errors are discarded.
        @param show_private: If true, then include private objects
            in the output; if false, then only include public objects.
        @param body_only: If true, then don't include navigation bars
            and breadcrumbs when generating the HTML documentation
            for a module or a class.  If false, then do include them.
        @param options: Options for the C{HTMLFormatter} object that
            will be used to generate the HTML.  See
            L{HTMLFormatter.__init__} for a complete list of options.
        """
        # Get the object's value, uid, and link.
        if isinstance(object, Link):
            link = object
            uid = link.target()
            value = uid.value()
        elif isinstance(object, UID):
            uid = object
            link = Link(object.shortname(), uid)
            value = uid.value()
        else:
            try:
                value = object
                uid = make_uid(object)
                link = Link(object.__name__, uid)
            except:
                raise TypeError, ('You must use a Link or UID object '+
                                  'to specify a '+type(link).__name__)

        # Redirect stderr.
        old_stderr = sys.stderr
        try:
            if error_stream is None: error_stream = StringIO()
            sys.stderr = error_stream
            
            # Create a docmap and add the object.
            docmap = DocMap()
            docmap.add(object)
            doc = docmap.get(uid)
    
            # Create a DocFormatter from the docmap.
            formatter = HTMLFormatter(docmap)
            
            # Get the object's HTML documentation.
            public = StringIO()
            private = StringIO()
            if show_private: outstream = private
            else: outstream = public
            if uid.is_module():
                formatter._write_module(public, private, uid, doc,
                                        body_only=body_only)
                htmlstr = outstream.getvalue()
            elif uid.is_class():
                formatter._write_class(public, private, uid, doc,
                                       body_only=body_only)
                htmlstr = outstream.getvalue()
            elif uid.is_property():
                formatter._write_property_details_entry(public, private, link,
                                                        uid.parent())
                htmlstr = outstream.getvalue()
            elif uid.is_routine():
                htmlstr = formatter._func_details_entry(link, None)
            else:
                raise ValueError("Don't know how to document %s" % object)

            return htmlstr
        finally:
            # Reset stderr & return the HTML documentation
            sys.stderr = old_stderr
            
    format = staticmethod(format)
    
##################################################
## Helper Functions & Classes
##################################################
        
def _write_if_nonempty(public, private, links, str):
    """
    Write C{str} to C{public} and C{private}; but only write to
    C{private} if C{links} is nonempty; and only write to
    C{public} if C{links} contains at least one public object.

    This helper function is used to write list headers etc., which
    should only appear if the list will actually contain something.
    """
    if len(links) == 0: return
    private.write(str)
    for link in links:
        if link.is_public():
            public.write(str)
            return

class _HTMLDocstringLinker(markup.DocstringLinker):
    def __init__(self, docformatter, uid):
        self._docformatter = docformatter
        self._uid = uid
        self._docmap = docformatter._docmap
        if uid.is_module() or uid.is_class() or uid.is_routine():
            self._container = uid
        else:
            self._container = uid.cls() or uid.module()
    def translate_indexterm(self, indexterm):
        key = self._docformatter._term_index_to_anchor(indexterm)
        return ('<a name="%s"></a><i class="indexterm">%s</i>' %
                (key, indexterm.to_html(self)))
    def translate_identifier_xref(self, identifier, label=None):
        if label is None: label = markup.plaintext_to_html(identifier)
        uid = findUID(identifier, self._container, self._docmap)
        if uid is None:
            failed_xrefs = self._docformatter._failed_xrefs
            if not failed_xrefs.has_key(identifier):
                failed_xrefs[identifier] = {}
            failed_xrefs[identifier][self._uid] = 1    
        return self._docformatter._uid_to_href(uid, label, 'link')
        
class _DevNull:
    """
    A file-like-object that ignores all input.  Used by L{HTMLFormatter}
    when not generating private docs, to throw away the private docs.
    """
    def write(self, s): 'Do nothing.'
    def close(self): 'Do nothing.'
    
