SF.net SVN: docutils:[9694 ] trunk/docutils

milde--- via Docutils-checkins <[email protected]>
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 9694
          http://sourceforge.net/p/docutils/code/9694
Author:   milde
Date:     2024-05-08 07:10:56 +0000 (Wed, 08 May 2024)
Log Message:
-----------
Doctree validation: declare valid element children (part 2).

Declare valid children and valid number of children for all
document tree elements.

Requires some re-ordering (define sub-elements first so that
the class can be used in the content declaration of the parent).

New element category class `nodes.PureTextElement` for nodes that do
not accept Inline elements, only text (was a TODO comment).

Add declarations for valid attributes of table elements defined
in the Exchange Table Model but not used/supported by Docutils.

Modified Paths:
--------------
    trunk/docutils/HISTORY.txt
    trunk/docutils/docutils/nodes.py

Modified: trunk/docutils/HISTORY.txt
===================================================================
--- trunk/docutils/HISTORY.txt	2024-05-08 07:10:49 UTC (rev 9693)
+++ trunk/docutils/HISTORY.txt	2024-05-08 07:10:56 UTC (rev 9694)
@@ -24,7 +24,7 @@
 
 * docutils/nodes.py
 
-  - New `SubStructural` element category class.
+  - New element category classes `SubStructural` and `PureTextElement`.
   - Fix element categories.
   - New method `Element.validate()` (work in progress).
   - New "attribute validating functions"

Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py	2024-05-08 07:10:49 UTC (rev 9693)
+++ trunk/docutils/docutils/nodes.py	2024-05-08 07:10:56 UTC (rev 9694)
@@ -1222,6 +1222,7 @@
 
 class Admonition(Body):
     """Admonitions (distinctive and self-contained notices)."""
+    valid_children = Body  # (%body.elements;)
 
 
 class Sequential(Body):
@@ -1248,6 +1249,7 @@
 
     Children of `decoration`.
     """
+    valid_children = Body  # (%body.elements;)
 
 
 class Inline:
@@ -1326,11 +1328,9 @@
         self.attributes['xml:space'] = 'preserve'
 
 
-# TODO: PureTextElement(TextElement):
-#   """An element which only contains text, no children."""
-#   For elements in the DTD that directly employ #PCDATA in their definition:
-#   citation_reference, comment, footnote_reference, label, math, math_block,
-#   option_argument, option_string, raw,
+class PureTextElement(TextElement):
+    """An element which only contains text, no children."""
+    valid_children = Text  # (#PCDATA)
 
 
 # ==============
@@ -1345,6 +1345,13 @@
     `docutils.utils.new_document()` instead.
     """
     valid_attributes = Element.valid_attributes + ('title',)
+    # content model: ( (title, subtitle?)?,
+    #                   meta*,
+    #                   decoration?,
+    #                   (docinfo, transition?)?,
+    #                   %structure.model; )
+    valid_children = (Structural, SubRoot, Body)
+    valid_len = (0, None)  # may be empty
 
     def __init__(self, settings, reporter, *args, **kwargs):
         Element.__init__(self, *args, **kwargs)
@@ -1748,8 +1755,11 @@
 #  Decorative Elements
 # =====================
 
+
 class decoration(PreBibliographic, SubRoot, Element):
-    """Container for header and footer."""
+    """Container for `header` and `footer`."""
+    valid_children = Decorative  # (header?, footer?)
+    valid_len = (0, 2)           # TODO: empty element does not make sense.
 
     def get_header(self):
         if not len(self.children) or not isinstance(self.children[0], header):
@@ -1772,6 +1782,8 @@
 
 class section(Structural, Element):
     """Document section. The main unit of hierarchy."""
+    # content model: (title, subtitle?, %structure.model;)
+    valid_children = (Structural, SubStructural, Body)
 
 
 class topic(Structural, Element):
@@ -1782,9 +1794,9 @@
     and it doesn't have to conform to section placement rules.
 
     Topics are allowed wherever body elements (list, table, etc.) are allowed,
-    but only at the top level of a section or document.  Topics cannot nest
-    inside topics, sidebars, or body elements; you can't have a topic inside a
-    table, list, block quote, etc.
+    but only at the top level of a sideber, section or document.
+    Topics cannot nest inside topics, or body elements; you can't have
+    a topic inside a table, list, block quote, etc.
     """
     # "depth" and "local" attributes may be added by the "Contents" transform:
     valid_attributes = Element.valid_attributes + ('depth', 'local')
@@ -1823,40 +1835,96 @@
 # ===============
 
 class paragraph(General, TextElement): pass
-class compound(General, Element): pass
-class container(General, Element): pass
 
 
+class compound(General, Element):
+    valid_children = Body  # (%body.elements;)+
+
+
+class container(General, Element):
+    valid_children = Body  # (%body.elements;)+
+
+
+class attribution(Part, TextElement):
+    """Visible reference to the source of a `block_quote`."""
+
+
+class block_quote(General, Element):
+    """An extended quotation, set off from the main text."""
+    valid_children = (Body, attribution)  # ((%body.elements;)+, attribution?)
+
+
+# Lists
+# =====
+#
+# Lists (Sequential) and related Body Subelements (Part)
+
+class list_item(Part, Element):
+    valid_children = Body  # (%body.elements;)*
+    valid_len = (0, None)
+
+
 class bullet_list(Sequential, Element):
     valid_attributes = Element.valid_attributes + ('bullet',)
+    valid_children = list_item  # (list_item+)
 
 
 class enumerated_list(Sequential, Element):
     valid_attributes = Element.valid_attributes + (
         'enumtype', 'prefix', 'suffix', 'start')
+    valid_children = list_item  # (list_item+)
 
 
-class list_item(Part, Element): pass
-class definition_list(Sequential, Element): pass
-class definition_list_item(Part, Element): pass
 class term(Part, TextElement): pass
 class classifier(Part, TextElement): pass
-class definition(Part, Element): pass
-class field_list(Sequential, Element): pass
-class field(Part, Bibliographic, Element): pass
+
+
+class definition(Part, Element):
+    """Definition of a `term` in a `definition_list`."""
+    valid_children = Body  # (%body.elements;)+
+
+
+class definition_list_item(Part, Element):
+    valid_children = (term, classifier, definition)
+    valid_len = (2, None)  # (term, classifier*, definition)
+
+
+class definition_list(Sequential, Element):
+    """List of terms and their definitions.
+
+    Can be used for glossaries or dictionaries, to describe or
+    classify things, for dialogues, or to itemize subtopics.
+    """
+    valid_children = definition_list_item  # (definition_list_item+)
+
+
 class field_name(Part, TextElement): pass
-class field_body(Part, Element): pass
 
 
-class option(Part, Element):
-    """Option element in an `option_list_item`.
+class field_body(Part, Element):
+    valid_children = Body  # (%body.elements;)*
+    valid_len = (0, None)
 
-    Groups an option string with zero or more option argument placeholders.
+
+class field(Part, Bibliographic, Element):
+    valid_children = (field_name, field_body)  # (field_name, field_body)
+    valid_len = (2, 2)
+
+
+class field_list(Sequential, Element):
+    """List of label & data pairs.
+
+    Typically rendered as a two-column list.
+    Also used for extension syntax or special processing.
     """
-    child_text_separator = ''
+    valid_children = field  # (field+)
 
 
-class option_argument(Part, TextElement):
+class option_string(Part, PureTextElement):
+    """A literal command-line option. Typically monospaced."""
+
+
+class option_argument(Part, PureTextElement):
     """Placeholder text for option arguments."""
     valid_attributes = Element.valid_attributes + ('delimiter',)
 
@@ -1864,13 +1932,25 @@
         return self.get('delimiter', ' ') + TextElement.astext(self)
 
 
+class option(Part, Element):
+    """Option element in an `option_list_item`.
+
+    Groups an option string with zero or more option argument placeholders.
+    """
+    child_text_separator = ''
+    # content model: (option_string, option_argument*)
+    valid_children = (option_string, option_argument)
+
+
 class option_group(Part, Element):
     """Groups together one or more `option` elements, all synonyms."""
     child_text_separator = ', '
+    valid_children = option  # (option+)
 
 
-class option_list(Sequential, Element):
-    """Two-column list of command-line options and descriptions."""
+class description(Part, Element):
+    """Describtion of a command-line option."""
+    valid_children = Body  # (%body.elements;)+
 
 
 class option_list_item(Part, Element):
@@ -1877,23 +1957,44 @@
     """Container for a pair of `option_group` and `description` elements.
     """
     child_text_separator = '  '
+    valid_children = (option_group, description)  # (option_group, description)
+    valid_len = (2, 2)
 
 
-class option_string(Part, TextElement): pass
-class description(Part, Element): pass
+class option_list(Sequential, Element):
+    """Two-column list of command-line options and descriptions."""
+    valid_children = option_list_item  # (option_list_item+)
+
+
+# Pre-formatted text blocks
+# =========================
+
 class literal_block(General, FixedTextElement): pass
 class doctest_block(General, FixedTextElement): pass
-class math_block(General, FixedTextElement): pass
-class line_block(General, Element): pass
 
 
+class math_block(General, FixedTextElement, PureTextElement):
+    """Mathematical notation (display formula)."""
+
+
 class line(Part, TextElement):
     """Single line of text in a `line_block`."""
     indent = None
 
 
-class block_quote(General, Element): pass
-class attribution(Part, TextElement): pass
+class line_block(General, Element):
+    """Sequence of lines and nested line blocks.
+    """
+    # recursive content model: (line | line_block)+
+
+
+line_block.valid_children = (line, line_block)
+
+
+# Admonitions
+# ===========
+# distinctive and self-contained notices
+
 class attention(Admonition, Element): pass
 class caution(Admonition, Element): pass
 class danger(Admonition, Element): pass
@@ -1903,10 +2004,20 @@
 class tip(Admonition, Element): pass
 class hint(Admonition, Element): pass
 class warning(Admonition, Element): pass
-class admonition(Admonition, Element): pass
-class comment(Invisible, FixedTextElement): pass
 
 
+class admonition(Admonition, Element):
+    valid_children = (title, Body)  # (title, (%body.elements;)+)
+    valid_len = (2, None)
+
+
+# Invisible elements
+# ==================
+
+class comment(Invisible, FixedTextElement, PureTextElement):
+    """Author notes, hidden from the output."""
+
+
 class substitution_definition(Invisible, TextElement):
     valid_attributes = Element.valid_attributes + ('ltrim', 'rtrim')
 
@@ -1916,47 +2027,114 @@
         'anonymous', 'refid', 'refname', 'refuri')
 
 
+# Footnote and citation
+# =====================
+
+class label(Part, PureTextElement):
+    """Visible identifier for footnotes and citations."""
+
+
 class footnote(General, BackLinkable, Element, Labeled, Targetable):
+    """Labelled note providing additional context (footnote or endnote)."""
     valid_attributes = Element.valid_attributes + ('auto', 'backrefs')
+    valid_children = (label, Body)  # (label?, (%body.elements;)+)
 
 
-class citation(General, BackLinkable, Element, Labeled, Targetable): pass
-class label(Part, TextElement): pass
+class citation(General, BackLinkable, Element, Labeled, Targetable):
+    valid_children = (label, Body)  # (label, (%body.elements;)+)
+    valid_len = (2, None)
 
 
-class figure(General, Element):
-    valid_attributes = Element.valid_attributes + ('align', 'width')
+# Graphical elements
+# ==================
 
+class image(General, Inline, Element):
+    """Reference to an image resource.
 
+    May be body element or inline element.
+    """
+    valid_attributes = Element.valid_attributes + (
+        'uri', 'alt', 'align', 'height', 'width', 'scale', 'loading')
+    valid_len = (0, 0)  # emtpy element
+
+    def astext(self):
+        return self.get('alt', '')
+
+
 class caption(Part, TextElement): pass
-class legend(Part, Element): pass
 
 
-class table(General, Element):
-    valid_attributes = Element.valid_attributes + (
-        'align', 'colsep', 'frame', 'pgwide', 'rowsep', 'width')
+class legend(Part, Element):
+    """A wrapper for text accompanying a `figure` that is not the caption."""
+    valid_children = Body  # (%body.elements;)
 
 
-class tgroup(Part, Element):
+class figure(General, Element):
+    """A formal figure, generally an illustration, with a title."""
+    valid_attributes = Element.valid_attributes + ('align', 'width')
+    # content model: (image, ((caption, legend?) | legend))
+    valid_children = (image, caption, legend)
+    valid_len = (1, 3)
+    # TODO: According to the DTD, a caption or legend is required
+    # but rST allows "bare" figures which are formatted differently from
+    # images (floating in LaTeX, nested in a <figure> in HTML).
+
+
+# Tables
+# ======
+
+class entry(Part, Element):
+    """An entry in a `row` (a table cell)."""
     valid_attributes = Element.valid_attributes + (
-        'align', 'cols', 'colsep', 'rowsep')
+        'align', 'char', 'charoff', 'colname', 'colsep', 'morecols',
+        'morerows', 'namest', 'nameend', 'rowsep', 'valign')
+    valid_children = Body  # %tbl.entry.mdl -> (%body.elements;)*
+    valid_len = (0, None)  # may be empty
 
 
+class row(Part, Element):
+    """Row of table cells."""
+    valid_attributes = Element.valid_attributes + ('rowsep', 'valign')
+    valid_children = entry  # (%tbl.row.mdl;) -> entry+
+
+
 class colspec(Part, Element):
+    """Specifications for a column in a `tgroup`."""
     valid_attributes = Element.valid_attributes + (
         'align', 'char', 'charoff', 'colname', 'colnum',
         'colsep', 'colwidth', 'rowsep', 'stub')
+    valid_len = (0, 0)  # empty element
 
 
-class thead(Part, Element): pass
-class tbody(Part, Element): pass
-class row(Part, Element): pass
+class thead(Part, Element):
+    """Row(s) that form the head of a `tgroup`."""
+    valid_attributes = Element.valid_attributes + ('valign',)
+    valid_children = row  # (row+)
 
 
-class entry(Part, Element):
-    valid_attributes = Element.valid_attributes + ('morecols', 'morerows')
+class tbody(Part, Element):
+    """Body of a `tgroup`."""
+    valid_attributes = Element.valid_attributes + ('valign',)
+    valid_children = row  # (row+)
 
 
+class tgroup(Part, Element):
+    """A portion of a table. Most tables have just one `tgroup`."""
+    valid_attributes = Element.valid_attributes + (
+        'align', 'cols', 'colsep', 'rowsep')
+    valid_children = (colspec, thead, tbody)  # (colspec*, thead?, tbody)
+
+
+class table(General, Element):
+    """A data arrangement with rows and columns."""
+    valid_attributes = Element.valid_attributes + (
+        'align', 'colsep', 'frame', 'pgwide', 'rowsep', 'width')
+    valid_children = (title, tgroup)  # (title?, tgroup+)
+
+
+# Special purpose elements
+# ========================
+
 class system_message(Special, BackLinkable, PreBibliographic, Element):
     """
     System message element.
@@ -2059,7 +2237,8 @@
         return obj
 
 
-class raw(Special, Inline, PreBibliographic, FixedTextElement):
+class raw(Special, Inline, PreBibliographic,
+          FixedTextElement, PureTextElement):
     """Raw data that is to be passed untouched to the Writer.
     """
     valid_attributes = Element.valid_attributes + ('format', 'xml:space')
@@ -2079,11 +2258,11 @@
         'anonymous', 'name', 'refid', 'refname', 'refuri')
 
 
-class footnote_reference(Inline, Referential, TextElement):
+class footnote_reference(Inline, Referential, PureTextElement):
     valid_attributes = Element.valid_attributes + ('auto', 'refid', 'refname')
 
 
-class citation_reference(Inline, Referential, TextElement):
+class citation_reference(Inline, Referential, PureTextElement):
     valid_attributes = Element.valid_attributes + ('refid', 'refname')
 
 
@@ -2096,19 +2275,12 @@
 class acronym(Inline, TextElement): pass
 class superscript(Inline, TextElement): pass
 class subscript(Inline, TextElement): pass
-class math(Inline, TextElement): pass
 
 
-class image(General, Inline, Element):
-    """Reference to an image resource."""
+class math(Inline, PureTextElement):
+    """Mathematical notation in running text."""
 
-    valid_attributes = Element.valid_attributes + (
-        'uri', 'alt', 'align', 'height', 'width', 'scale', 'loading')
 
-    def astext(self):
-        return self.get('alt', '')
-
-
 class inline(Inline, TextElement): pass
 
 

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.