|
From: <mi...@us...> - 2024-05-08 07:10:59
|
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.
|