Update of /cvs-repository/Zope/lib/python/docutils/parsers/rst/directives
In directory cvs.zope.org:/tmp/cvs-serv26422/parsers/rst/directives

Modified Files:
      Tag: Zope-2_7-branch
        __init__.py admonitions.py body.py html.py images.py misc.py 
        parts.py references.py tables.py 
Log Message:
upgrade to docutils 0.3.9


=== Zope/lib/python/docutils/parsers/rst/directives/__init__.py 1.2.10.8 => 
1.2.10.9 ===
--- Zope/lib/python/docutils/parsers/rst/directives/__init__.py:1.2.10.8        
Fri Jan  7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/__init__.py Sun Oct  9 
10:43:45 2005
@@ -113,10 +113,13 @@
       #'questions': ('body', 'question_list'),
       'table': ('tables', 'table'),
       'csv-table': ('tables', 'csv_table'),
+      'list-table': ('tables', 'list_table'),
       'image': ('images', 'image'),
       'figure': ('images', 'figure'),
       'contents': ('parts', 'contents'),
       'sectnum': ('parts', 'sectnum'),
+      'header': ('parts', 'header'),
+      'footer': ('parts', 'footer'),
       #'footnotes': ('parts', 'footnotes'),
       #'citations': ('parts', 'citations'),
       'target-notes': ('references', 'target_notes'),
@@ -250,17 +253,26 @@
     Return the path argument unwrapped (with newlines removed).
     (Directive option conversion function.)
 
-    Raise ``ValueError`` if no argument is found or if the path contains
-    internal whitespace.
+    Raise ``ValueError`` if no argument is found.
     """
     if argument is None:
         raise ValueError('argument required but none supplied')
     else:
         path = ''.join([s.strip() for s in argument.splitlines()])
-        if path.find(' ') == -1:
-            return path
-        else:
-            raise ValueError('path contains whitespace')
+        return path
+
+def uri(argument):
+    """
+    Return the URI argument with whitespace removed.
+    (Directive option conversion function.)
+
+    Raise ``ValueError`` if no argument is found.
+    """
+    if argument is None:
+        raise ValueError('argument required but none supplied')
+    else:
+        uri = ''.join(argument.split())
+        return uri
 
 def nonnegative_int(argument):
     """
@@ -274,7 +286,7 @@
 
 def class_option(argument):
     """
-    Convert the argument into an ID-compatible string and return it.
+    Convert the argument into a list of ID-compatible strings and return it.
     (Directive option conversion function.)
 
     Raise ``ValueError`` if no argument is found.
@@ -288,7 +300,7 @@
         if not class_name:
             raise ValueError('cannot make "%s" into a class name' % name)
         class_names.append(class_name)
-    return ' '.join(class_names)
+    return class_names
 
 unicode_pattern = re.compile(
     r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE)
@@ -296,10 +308,13 @@
 def unicode_code(code):
     r"""
     Convert a Unicode character code to a Unicode character.
+    (Directive option conversion function.)
 
     Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``,
     ``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style
     numeric character entities (e.g. ``☮``).  Other text remains as-is.
+
+    Raise ValueError for illegal Unicode code values.
     """
     try:
         if code.isdigit():                  # decimal number
@@ -315,6 +330,10 @@
         raise ValueError('code too large (%s)' % detail)
 
 def single_char_or_unicode(argument):
+    """
+    A single character is returned as-is.  Unicode characters codes are
+    converted as in `unicode_code`.  (Directive option conversion function.)
+    """
     char = unicode_code(argument)
     if len(char) > 1:
         raise ValueError('%r invalid; must be a single character or '
@@ -322,6 +341,10 @@
     return char
 
 def single_char_or_whitespace_or_unicode(argument):
+    """
+    As with `single_char_or_unicode`, but "tab" and "space" are also supported.
+    (Directive option conversion function.)
+    """
     if argument == 'tab':
         char = '\t'
     elif argument == 'space':
@@ -331,12 +354,23 @@
     return char
 
 def positive_int(argument):
+    """
+    Converts the argument into an integer.  Raises ValueError for negative,
+    zero, or non-integer values.  (Directive option conversion function.)
+    """
     value = int(argument)
     if value < 1:
         raise ValueError('negative or zero value; must be positive')
     return value
 
 def positive_int_list(argument):
+    """
+    Converts a space- or comma-separated list of values into a Python list
+    of integers.
+    (Directive option conversion function.)
+
+    Raises ValueError for non-positive-integer values.
+    """
     if ',' in argument:
         entries = argument.split(',')
     else:
@@ -344,6 +378,12 @@
     return [positive_int(entry) for entry in entries]
 
 def encoding(argument):
+    """
+    Verfies the encoding argument by lookup.
+    (Directive option conversion function.)
+
+    Raises ValueError for unknown encodings.
+    """
     try:
         codecs.lookup(argument)
     except LookupError:


=== Zope/lib/python/docutils/parsers/rst/directives/admonitions.py 1.2.10.6 => 
1.2.10.7 ===
--- Zope/lib/python/docutils/parsers/rst/directives/admonitions.py:1.2.10.6     
Fri Jan  7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/admonitions.py      Sun Oct 
 9 10:43:45 2005
@@ -30,10 +30,10 @@
         admonition_node += nodes.title(title_text, '', *textnodes)
         admonition_node += messages
         if options.has_key('class'):
-            class_value = options['class']
+            classes = options['class']
         else:
-            class_value = 'admonition-' + nodes.make_id(title_text)
-        admonition_node.set_class(class_value)
+            classes = ['admonition-' + nodes.make_id(title_text)]
+        admonition_node['classes'] += classes
     state.nested_parse(content, content_offset, admonition_node)
     return [admonition_node]
 


=== Zope/lib/python/docutils/parsers/rst/directives/body.py 1.2.10.7 => 
1.2.10.8 ===
--- Zope/lib/python/docutils/parsers/rst/directives/body.py:1.2.10.7    Fri Jan 
 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/body.py     Sun Oct  9 
10:43:45 2005
@@ -16,14 +16,16 @@
 import sys
 from docutils import nodes
 from docutils.parsers.rst import directives
+from docutils.parsers.rst.roles import set_classes
 
               
 def topic(name, arguments, options, content, lineno,
           content_offset, block_text, state, state_machine,
           node_class=nodes.topic):
-    if not state_machine.match_titles:
+    if not (state_machine.match_titles
+            or isinstance(state_machine.node, nodes.sidebar)):
         error = state_machine.reporter.error(
-              'The "%s" directive may not be used within topics, sidebars, '
+              'The "%s" directive may not be used within topics '
               'or body elements.' % name,
               nodes.literal_block(block_text, block_text), line=lineno)
         return [error]
@@ -44,8 +46,7 @@
         messages.extend(more_messages)
     text = '\n'.join(content)
     node = node_class(text, *(titles + messages))
-    if options.has_key('class'):
-        node.set_class(options['class'])
+    node['classes'] += options.get('class', [])
     if text:
         state.nested_parse(content, content_offset, node)
     return [node]
@@ -56,6 +57,11 @@
 
 def sidebar(name, arguments, options, content, lineno,
             content_offset, block_text, state, state_machine):
+    if isinstance(state_machine.node, nodes.sidebar):
+        error = state_machine.reporter.error(
+              'The "%s" directive may not be used within a sidebar element.'
+              % name, nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
     return topic(name, arguments, options, content, lineno,
                  content_offset, block_text, state, state_machine,
                  node_class=nodes.sidebar)
@@ -72,7 +78,7 @@
             'Content block expected for the "%s" directive; none found.'
             % name, nodes.literal_block(block_text, block_text), line=lineno)
         return [warning]
-    block = nodes.line_block()
+    block = nodes.line_block(classes=options.get('class', []))
     node_list = [block]
     for line_text in content:
         text_nodes, messages = state.inline_text(line_text.strip(),
@@ -91,6 +97,7 @@
 
 def parsed_literal(name, arguments, options, content, lineno,
                    content_offset, block_text, state, state_machine):
+    set_classes(options)
     return block(name, arguments, options, content, lineno,
                  content_offset, block_text, state, state_machine,
                  node_class=nodes.literal_block)
@@ -124,7 +131,7 @@
 def epigraph(name, arguments, options, content, lineno,
              content_offset, block_text, state, state_machine):
     block_quote, messages = state.block_quote(content, content_offset)
-    block_quote.set_class('epigraph')
+    block_quote['classes'].append('epigraph')
     return [block_quote] + messages
 
 epigraph.content = 1
@@ -132,7 +139,7 @@
 def highlights(name, arguments, options, content, lineno,
              content_offset, block_text, state, state_machine):
     block_quote, messages = state.block_quote(content, content_offset)
-    block_quote.set_class('highlights')
+    block_quote['classes'].append('highlights')
     return [block_quote] + messages
 
 highlights.content = 1
@@ -140,7 +147,7 @@
 def pull_quote(name, arguments, options, content, lineno,
              content_offset, block_text, state, state_machine):
     block_quote, messages = state.block_quote(content, content_offset)
-    block_quote.set_class('pull-quote')
+    block_quote['classes'].append('pull-quote')
     return [block_quote] + messages
 
 pull_quote.content = 1
@@ -154,8 +161,7 @@
             nodes.literal_block(block_text, block_text), line=lineno)
         return [error]
     node = nodes.compound(text)
-    if options.has_key('class'):
-        node.set_class(options['class'])
+    node['classes'] += options.get('class', [])
     state.nested_parse(content, content_offset, node)
     return [node]
 


=== Zope/lib/python/docutils/parsers/rst/directives/html.py 1.2.10.6 => 
1.2.10.7 ===
--- Zope/lib/python/docutils/parsers/rst/directives/html.py:1.2.10.6    Fri Jan 
 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/html.py     Sun Oct  9 
10:43:45 2005
@@ -34,7 +34,7 @@
             'Empty meta directive.',
             nodes.literal_block(block_text, block_text), line=lineno)
         node += error
-    return node.get_children()
+    return node.children
 
 meta.content = 1
 


=== Zope/lib/python/docutils/parsers/rst/directives/images.py 1.2.10.7 => 
1.2.10.8 ===
--- Zope/lib/python/docutils/parsers/rst/directives/images.py:1.2.10.7  Fri Jan 
 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/images.py   Sun Oct  9 
10:43:45 2005
@@ -14,27 +14,43 @@
 import sys
 from docutils import nodes, utils
 from docutils.parsers.rst import directives, states
-from docutils.nodes import whitespace_normalize_name
+from docutils.nodes import fully_normalize_name
+from docutils.parsers.rst.roles import set_classes
 
 try:
     import Image                        # PIL
 except ImportError:
     Image = None
 
-align_values = ('top', 'middle', 'bottom', 'left', 'center', 'right')
+align_h_values = ('left', 'center', 'right')
+align_v_values = ('top', 'middle', 'bottom')
+align_values = align_v_values + align_h_values
 
 def align(argument):
     return directives.choice(argument, align_values)
 
 def image(name, arguments, options, content, lineno,
           content_offset, block_text, state, state_machine):
+    if options.has_key('align'):
+        # check for align_v values only
+        if isinstance(state, states.SubstitutionDef):
+            if options['align'] not in align_v_values:
+                error = state_machine.reporter.error(
+                    'Error in "%s" directive: "%s" is not a valid value for '
+                    'the "align" option within a substitution definition.  '
+                    'Valid values for "align" are: "%s".'
+                    % (name, options['align'], '", "'.join(align_v_values)),
+                    nodes.literal_block(block_text, block_text), line=lineno)
+                return [error]
+        elif options['align'] not in align_h_values:
+            error = state_machine.reporter.error(
+                'Error in "%s" directive: "%s" is not a valid value for '
+                'the "align" option.  Valid values for "align" are: "%s".'
+                % (name, options['align'], '", "'.join(align_h_values)),
+                nodes.literal_block(block_text, block_text), line=lineno)
+            return [error]
     messages = []
-    reference = ''.join(arguments[0].split('\n'))
-    if reference.find(' ') != -1:
-        error = state_machine.reporter.error(
-              'Image URI contains whitespace.',
-              nodes.literal_block(block_text, block_text), line=lineno)
-        return [error]
+    reference = directives.uri(arguments[0])
     options['uri'] = reference
     reference_node = None
     if options.has_key('target'):
@@ -44,12 +60,13 @@
         if target_type == 'refuri':
             reference_node = nodes.reference(refuri=data)
         elif target_type == 'refname':
-            reference_node = nodes.reference(
-                refname=data, 
name=whitespace_normalize_name(options['target']))
+            reference_node = nodes.reference(refname=data,
+                name=fully_normalize_name(options['target']))
             state.document.note_refname(reference_node)
         else:                           # malformed target
             messages.append(data)       # data is a system message
         del options['target']
+    set_classes(options)
     image_node = nodes.image(block_text, **options)
     if reference_node:
         reference_node += image_node
@@ -66,31 +83,38 @@
                  'target': directives.unchanged_required,
                  'class': directives.class_option}
 
+def figure_align(argument):
+    return directives.choice(argument, align_h_values)
+
 def figure(name, arguments, options, content, lineno,
            content_offset, block_text, state, state_machine):
     figwidth = options.setdefault('figwidth')
-    figclass = options.setdefault('figclass')
+    figclasses = options.setdefault('figclass')
+    align = options.setdefault('align')
     del options['figwidth']
     del options['figclass']
+    del options['align']
     (image_node,) = image(name, arguments, options, content, lineno,
                          content_offset, block_text, state, state_machine)
     if isinstance(image_node, nodes.system_message):
         return [image_node]
     figure_node = nodes.figure('', image_node)
     if figwidth == 'image':
-        if Image:
+        if Image and state.document.settings.file_insertion_enabled:
             # PIL doesn't like Unicode paths:
             try:
                 i = Image.open(str(image_node['uri']))
             except (IOError, UnicodeError):
                 pass
             else:
-                state.document.settings.record_dependencies.add(reference)
+                
state.document.settings.record_dependencies.add(image_node['uri'])
                 figure_node['width'] = i.size[0]
     elif figwidth is not None:
         figure_node['width'] = figwidth
-    if figclass:
-        figure_node.set_class(figclass)
+    if figclasses:
+        figure_node['classes'] += figclasses
+    if align:
+        figure_node['align'] = align
     if content:
         node = nodes.Element()          # anonymous container for parsing
         state.nested_parse(content, content_offset, node)
@@ -119,4 +143,5 @@
 figure.options = {'figwidth': figwidth_value,
                   'figclass': directives.class_option}
 figure.options.update(image.options)
+figure.options['align'] = figure_align
 figure.content = 1


=== Zope/lib/python/docutils/parsers/rst/directives/misc.py 1.2.10.7 => 
1.2.10.8 ===
--- Zope/lib/python/docutils/parsers/rst/directives/misc.py:1.2.10.7    Fri Jan 
 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/misc.py     Sun Oct  9 
10:43:45 2005
@@ -24,15 +24,15 @@
 def include(name, arguments, options, content, lineno,
             content_offset, block_text, state, state_machine):
     """Include a reST file as part of the content of this reST file."""
+    if not state.document.settings.file_insertion_enabled:
+        warning = state_machine.reporter.warning(
+              '"%s" directive disabled.' % name,
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [warning]
     source = state_machine.input_lines.source(
         lineno - state_machine.input_offset - 1)
     source_dir = os.path.dirname(os.path.abspath(source))
-    path = ''.join(arguments[0].splitlines())
-    if path.find(' ') != -1:
-        error = state_machine.reporter.error(
-              '"%s" directive path contains whitespace.' % name,
-              nodes.literal_block(block_text, block_text), line=lineno)
-        return [error]
+    path = directives.path(arguments[0])
     path = os.path.normpath(os.path.join(source_dir, path))
     path = utils.relative_path(None, path)
     encoding = options.get('encoding', state.document.settings.input_encoding)
@@ -48,7 +48,14 @@
               % (name, error.__class__.__name__, error),
               nodes.literal_block(block_text, block_text), line=lineno)
         return [severe]
-    include_text = include_file.read()
+    try:
+        include_text = include_file.read()
+    except UnicodeError, error:
+        severe = state_machine.reporter.severe(
+              'Problem with "%s" directive:\n%s: %s'
+              % (name, error.__class__.__name__, error),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
     if options.has_key('literal'):
         literal_block = nodes.literal_block(include_text, include_text,
                                             source=path)
@@ -74,6 +81,13 @@
     Content may be included inline (content section of directive) or
     imported from a file or url.
     """
+    if ( not state.document.settings.raw_enabled
+         or (not state.document.settings.file_insertion_enabled
+             and (options.has_key('file') or options.has_key('url'))) ):
+        warning = state_machine.reporter.warning(
+              '"%s" directive disabled.' % name,
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [warning]
     attributes = {'format': ' '.join(arguments[0].lower().split())}
     encoding = options.get('encoding', state.document.settings.input_encoding)
     if content:
@@ -106,7 +120,14 @@
                   'Problems with "%s" directive path:\n%s.' % (name, error),
                   nodes.literal_block(block_text, block_text), line=lineno)
             return [severe]
-        text = raw_file.read()
+        try:
+            text = raw_file.read()
+        except UnicodeError, error:
+            severe = state_machine.reporter.severe(
+                  'Problem with "%s" directive:\n%s: %s'
+                  % (name, error.__class__.__name__, error),
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return [severe]
         attributes['source'] = path
     elif options.has_key('url'):
         if not urllib2:
@@ -128,7 +149,14 @@
         raw_file = io.StringInput(
             source=raw_text, source_path=source, encoding=encoding,
             error_handler=state.document.settings.input_encoding_error_handler)
-        text = raw_file.read()
+        try:
+            text = raw_file.read()
+        except UnicodeError, error:
+            severe = state_machine.reporter.severe(
+                  'Problem with "%s" directive:\n%s: %s'
+                  % (name, error.__class__.__name__, error),
+                  nodes.literal_block(block_text, block_text), line=lineno)
+            return [severe]
         attributes['source'] = source
     else:
         error = state_machine.reporter.warning(
@@ -140,7 +168,7 @@
 
 raw.arguments = (1, 0, 1)
 raw.options = {'file': directives.path,
-               'url': directives.path,
+               'url': directives.uri,
                'encoding': directives.encoding}
 raw.content = 1
 
@@ -160,8 +188,7 @@
             messages = []
             for node in element:
                 if isinstance(node, nodes.system_message):
-                    if node.has_key('backrefs'):
-                        del node['backrefs']
+                    node['backrefs'] = []
                     messages.append(node)
             error = state_machine.reporter.error(
                 'Error in "%s" directive: may contain a single paragraph '


=== Zope/lib/python/docutils/parsers/rst/directives/parts.py 1.2.10.6 => 
1.2.10.7 ===
--- Zope/lib/python/docutils/parsers/rst/directives/parts.py:1.2.10.6   Fri Jan 
 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/parts.py    Sun Oct  9 
10:43:45 2005
@@ -26,10 +26,24 @@
 
 def contents(name, arguments, options, content, lineno,
              content_offset, block_text, state, state_machine):
-    """Table of contents."""
+    """
+    Table of contents.
+
+    The table of contents is generated in two passes: initial parse and
+    transform.  During the initial parse, a 'pending' element is generated
+    which acts as a placeholder, storing the TOC title and any options
+    internally.  At a later stage in the processing, the 'pending' element is
+    replaced by a 'topic' element, a title and the table of contents proper.
+    """
+    if not (state_machine.match_titles
+            or isinstance(state_machine.node, nodes.sidebar)):
+        error = state_machine.reporter.error(
+              'The "%s" directive may not be used within topics '
+              'or body elements.' % name,
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
     document = state_machine.document
     language = languages.get_language(document.settings.language_code)
-
     if arguments:
         title_text = arguments[0]
         text_nodes, messages = state.inline_text(title_text, lineno)
@@ -40,24 +54,17 @@
             title = None
         else:
             title = nodes.title('', language.labels['contents'])
-
-    topic = nodes.topic(CLASS='contents')
-
-    cls = options.get('class')
-    if cls:
-        topic.set_class(cls)
-
+    topic = nodes.topic(classes=['contents'])
+    topic['classes'] += options.get('class', [])
     if title:
         name = title.astext()
         topic += title
     else:
         name = language.labels['contents']
-
     name = nodes.fully_normalize_name(name)
     if not document.has_name(name):
-        topic['name'] = name
+        topic['names'].append(name)
     document.note_implicit_target(topic)
-
     pending = nodes.pending(parts.Contents, rawsource=block_text)
     pending.details.update(options)
     document.note_pending(pending)
@@ -82,3 +89,36 @@
                    'start': int,
                    'prefix': directives.unchanged_required,
                    'suffix': directives.unchanged_required}
+
+def header_footer(node, name, arguments, options, content, lineno,
+                  content_offset, block_text, state, state_machine):
+    """Contents of document header or footer."""
+    if not content:
+        warning = state_machine.reporter.warning(
+            'Content block expected for the "%s" directive; none found.'
+            % name, nodes.literal_block(block_text, block_text),
+            line=lineno)
+        node.append(nodes.paragraph(
+            '', 'Problem with the "%s" directive: no content supplied.' % 
name))
+        return [warning]
+    text = '\n'.join(content)
+    state.nested_parse(content, content_offset, node)
+    return []
+
+def header(name, arguments, options, content, lineno,
+           content_offset, block_text, state, state_machine):
+    decoration = state_machine.document.get_decoration()
+    node = decoration.get_header()
+    return header_footer(node, name, arguments, options, content, lineno,
+                         content_offset, block_text, state, state_machine)
+
+header.content = 1
+
+def footer(name, arguments, options, content, lineno,
+           content_offset, block_text, state, state_machine):
+    decoration = state_machine.document.get_decoration()
+    node = decoration.get_footer()
+    return header_footer(node, name, arguments, options, content, lineno,
+                         content_offset, block_text, state, state_machine)
+
+footer.content = 1


=== Zope/lib/python/docutils/parsers/rst/directives/references.py 1.2.10.6 => 
1.2.10.7 ===


=== Zope/lib/python/docutils/parsers/rst/directives/tables.py 1.1.2.3 => 
1.1.2.4 ===
--- Zope/lib/python/docutils/parsers/rst/directives/tables.py:1.1.2.3   Fri Jan 
 7 08:26:04 2005
+++ Zope/lib/python/docutils/parsers/rst/directives/tables.py   Sun Oct  9 
10:43:45 2005
@@ -44,7 +44,6 @@
         return [warning]
     title, messages = make_title(arguments, state, lineno)
     node = nodes.Element()          # anonymous container for parsing
-    text = '\n'.join(content)
     state.nested_parse(content, content_offset, node)
     if len(node) != 1 or not isinstance(node[0], nodes.table):
         error = state_machine.reporter.error(
@@ -54,8 +53,7 @@
             line=lineno)
         return [error]
     table_node = node[0]
-    if options.has_key('class'):
-        table_node.set_class(options['class'])
+    table_node['classes'] += options.get('class', [])
     if title:
         table_node.insert(0, title)
     return [table_node] + messages
@@ -116,6 +114,12 @@
 def csv_table(name, arguments, options, content, lineno,
              content_offset, block_text, state, state_machine):
     try:
+        if ( not state.document.settings.file_insertion_enabled
+             and (options.has_key('file') or options.has_key('url')) ):
+            warning = state_machine.reporter.warning(
+                '"%s" directive disabled.' % name,
+                nodes.literal_block(block_text, block_text), line=lineno)
+            return [warning]
         check_requirements(name, lineno, block_text, state_machine)
         title, messages = make_title(arguments, state, lineno)
         csv_data, source = get_csv_data(
@@ -126,8 +130,10 @@
             csv_data, DocutilsDialect(options), source, options)
         max_cols = max(max_cols, max_header_cols)
         header_rows = options.get('header-rows', 0) # default 0
+        stub_columns = options.get('stub-columns', 0) # default 0
         check_table_dimensions(
-            rows, header_rows, name, lineno, block_text, state_machine)
+            rows, header_rows, stub_columns, name, lineno,
+            block_text, state_machine)
         table_head.extend(rows[:header_rows])
         table_body = rows[header_rows:]
         col_widths = get_column_widths(
@@ -141,19 +147,19 @@
             nodes.literal_block(block_text, block_text), line=lineno)
         return [error]
     table = (col_widths, table_head, table_body)
-    table_node = state.build_table(table, content_offset)
-    if options.has_key('class'):
-        table_node.set_class(options['class'])
+    table_node = state.build_table(table, content_offset, stub_columns)
+    table_node['classes'] += options.get('class', [])
     if title:
         table_node.insert(0, title)
     return [table_node] + messages
 
 csv_table.arguments = (0, 1, 1)
 csv_table.options = {'header-rows': directives.nonnegative_int,
+                     'stub-columns': directives.nonnegative_int,
                      'header': directives.unchanged,
                      'widths': directives.positive_int_list,
                      'file': directives.path,
-                     'url': directives.path,
+                     'url': directives.uri,
                      'encoding': directives.encoding,
                      'class': directives.class_option,
                      # field delimiter char
@@ -206,7 +212,8 @@
             state.document.settings.record_dependencies.add(source)
             csv_file = io.FileInput(
                 source_path=source, encoding=encoding,
-                
error_handler=state.document.settings.input_encoding_error_handler,
+                error_handler
+                    =state.document.settings.input_encoding_error_handler,
                 handle_io_errors=None)
             csv_data = csv_file.read().splitlines()
         except IOError, error:
@@ -270,20 +277,34 @@
         max_cols = max(max_cols, len(row))
     return rows, max_cols
 
-def check_table_dimensions(rows, header_rows, name, lineno, block_text,
-                           state_machine):
+def check_table_dimensions(rows, header_rows, stub_columns, name, lineno,
+                           block_text, state_machine):
     if len(rows) < header_rows:
         error = state_machine.reporter.error(
             '%s header row(s) specified but only %s row(s) of data supplied '
             '("%s" directive).' % (header_rows, len(rows), name),
             nodes.literal_block(block_text, block_text), line=lineno)
         raise SystemMessagePropagation(error)
-    elif len(rows) == header_rows > 0:
+    if len(rows) == header_rows > 0:
         error = state_machine.reporter.error(
             'Insufficient data supplied (%s row(s)); no data remaining for '
             'table body, required by "%s" directive.' % (len(rows), name),
             nodes.literal_block(block_text, block_text), line=lineno)
         raise SystemMessagePropagation(error)
+    for row in rows:
+        if len(row) < stub_columns:
+            error = state_machine.reporter.error(
+                '%s stub column(s) specified but only %s columns(s) of data '
+                'supplied ("%s" directive).' % (stub_columns, len(row), name),
+                nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(error)
+        if len(row) == stub_columns > 0:
+            error = state_machine.reporter.error(
+                'Insufficient data supplied (%s columns(s)); no data remaining 
'
+                'for table body, required by "%s" directive.'
+                % (len(row), name),
+                nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(error)
 
 def get_column_widths(max_cols, name, options, lineno, block_text,
                       state_machine):
@@ -295,8 +316,13 @@
               % (name, max_cols),
               nodes.literal_block(block_text, block_text), line=lineno)
             raise SystemMessagePropagation(error)
-    else:
+    elif max_cols:
         col_widths = [100 / max_cols] * max_cols
+    else:
+        error = state_machine.reporter.error(
+            'No table data detected in CSV file.',
+            nodes.literal_block(block_text, block_text), line=lineno)
+        raise SystemMessagePropagation(error)
     return col_widths
 
 def extend_short_rows_with_empty_cells(columns, parts):
@@ -304,3 +330,112 @@
         for row in part:
             if len(row) < columns:
                 row.extend([(0, 0, 0, [])] * (columns - len(row)))
+
+def list_table(name, arguments, options, content, lineno,
+               content_offset, block_text, state, state_machine):
+    """
+    Implement tables whose data is encoded as a uniform two-level bullet list.
+    For further ideas, see
+    http://docutils.sf.net/docs/dev/rst/alternatives.html#list-driven-tables
+    """ 
+    if not content:
+        error = state_machine.reporter.error(
+            'The "%s" directive is empty; content required.' % name,
+            nodes.literal_block(block_text, block_text), line=lineno)
+        return [error]
+    title, messages = make_title(arguments, state, lineno)
+    node = nodes.Element()          # anonymous container for parsing
+    state.nested_parse(content, content_offset, node)
+    try:
+        num_cols, col_widths = check_list_content(
+            node, name, options, content, lineno, block_text, state_machine)
+        table_data = [[item.children for item in row_list[0]]
+                      for row_list in node[0]]
+        header_rows = options.get('header-rows', 0) # default 0
+        stub_columns = options.get('stub-columns', 0) # default 0
+        check_table_dimensions(
+            table_data, header_rows, stub_columns, name, lineno,
+            block_text, state_machine)
+    except SystemMessagePropagation, detail:
+        return [detail.args[0]]
+    table_node = build_table_from_list(table_data, col_widths,
+                                       header_rows, stub_columns)
+    table_node['classes'] += options.get('class', [])
+    if title:
+        table_node.insert(0, title)
+    return [table_node] + messages
+
+list_table.arguments = (0, 1, 1)
+list_table.options = {'header-rows': directives.nonnegative_int,
+                      'stub-columns': directives.nonnegative_int,
+                      'widths': directives.positive_int_list,
+                      'class': directives.class_option}
+list_table.content = 1
+
+def check_list_content(node, name, options, content, lineno, block_text,
+                       state_machine):
+    if len(node) != 1 or not isinstance(node[0], nodes.bullet_list):
+        error = state_machine.reporter.error(
+            'Error parsing content block for the "%s" directive: '
+            'exactly one bullet list expected.' % name,
+            nodes.literal_block(block_text, block_text), line=lineno)
+        raise SystemMessagePropagation(error)
+    list_node = node[0]
+    # Check for a uniform two-level bullet list:
+    for item_index in range(len(list_node)):
+        item = list_node[item_index]
+        if len(item) != 1 or not isinstance(item[0], nodes.bullet_list):
+            error = state_machine.reporter.error(
+                'Error parsing content block for the "%s" directive: '
+                'two-level bullet list expected, but row %s does not contain '
+                'a second-level bullet list.' % (name, item_index + 1),
+                nodes.literal_block(block_text, block_text), line=lineno)
+            raise SystemMessagePropagation(error)
+        elif item_index:
+            if len(item[0]) != num_cols:
+                error = state_machine.reporter.error(
+                    'Error parsing content block for the "%s" directive: '
+                    'uniform two-level bullet list expected, but row %s does '
+                    'not contain the same number of items as row 1 (%s vs %s).'
+                    % (name, item_index + 1, len(item[0]), num_cols),
+                    nodes.literal_block(block_text, block_text), line=lineno)
+                raise SystemMessagePropagation(error)
+        else:
+            num_cols = len(item[0])
+    col_widths = get_column_widths(
+        num_cols, name, options, lineno, block_text, state_machine)
+    if len(col_widths) != num_cols:
+        error = state_machine.reporter.error(
+            'Error parsing "widths" option of the "%s" directive: '
+            'number of columns does not match the table data (%s vs %s).'
+            % (name, len(col_widths), num_cols),
+            nodes.literal_block(block_text, block_text), line=lineno)
+        raise SystemMessagePropagation(error)
+    return num_cols, col_widths
+
+def build_table_from_list(table_data, col_widths, header_rows, stub_columns):
+    table = nodes.table()
+    tgroup = nodes.tgroup(cols=len(col_widths))
+    table += tgroup
+    for col_width in col_widths:
+        colspec = nodes.colspec(colwidth=col_width)
+        if stub_columns:
+            colspec.attributes['stub'] = 1
+            stub_columns -= 1
+        tgroup += colspec
+    rows = []
+    for row in table_data:
+        row_node = nodes.row()
+        for cell in row:
+            entry = nodes.entry()
+            entry += cell
+            row_node += entry
+        rows.append(row_node)
+    if header_rows:
+        thead = nodes.thead()
+        thead.extend(rows[:header_rows])
+        tgroup += thead
+    tbody = nodes.tbody()
+    tbody.extend(rows[header_rows:])
+    tgroup += tbody
+    return table

_______________________________________________
Zope-Checkins maillist  -  Zope-Checkins@zope.org
http://mail.zope.org/mailman/listinfo/zope-checkins

Reply via email to