|
From: <mi...@us...> - 2024-06-05 15:54:32
|
Revision: 9732
http://sourceforge.net/p/docutils/code/9732
Author: milde
Date: 2024-06-05 15:54:27 +0000 (Wed, 05 Jun 2024)
Log Message:
-----------
Doctree validation: additional checks for `subtitle` and `transition` position.
A `subtitle` must not be used without preceding `title`.
A `transition` must not be used at the start or end of a section or document,
or after another transition.
Modified Paths:
--------------
trunk/docutils/HISTORY.txt
trunk/docutils/docutils/nodes.py
trunk/docutils/test/test_nodes.py
Modified: trunk/docutils/HISTORY.txt
===================================================================
--- trunk/docutils/HISTORY.txt 2024-06-05 15:54:16 UTC (rev 9731)
+++ trunk/docutils/HISTORY.txt 2024-06-05 15:54:27 UTC (rev 9732)
@@ -30,8 +30,8 @@
- New element category classes `SubStructural` and `PureTextElement`.
- Fix element categories.
- New method `Element.validate()`: raise `nodes.ValidationError` if
- the element does not comply with the "Docutils Document Model"
- (work in progress).
+ the element does not comply with the "Docutils Document Model".
+ Provisional.
- New "attribute validating functions"
convert string representations to correct data type,
normalize values,
Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py 2024-06-05 15:54:16 UTC (rev 9731)
+++ trunk/docutils/docutils/nodes.py 2024-06-05 15:54:27 UTC (rev 9732)
@@ -399,9 +399,14 @@
return self.__class__(str.lstrip(self, chars))
def validate(self, recursive=True):
- pass # Text nodes have no attributes and no children.
+ """Validate Docutils Document Tree element ("doctree")."""
+ # Text nodes have no attributes and no children.
+ def check_position(self):
+ """Hook for additional checks of the parent's content model."""
+ # no special placement requirements for Text nodes
+
class Element(Node):
"""
`Element` is the superclass to all specific elements.
@@ -1166,8 +1171,9 @@
problematic_element=child)
else: # quantifier in ('?', '*') -> optional child
continue # try same child with next part of content model
- # TODO: check additional placement constraints (if applicable)
- # child.check_position()
+ else:
+ # Check additional placement constraints (if applicable):
+ child.check_position()
# advance:
if quantifier in ('.', '?'): # go to next element
child = next(ichildren, None)
@@ -1175,6 +1181,7 @@
for child in ichildren:
if not isinstance(child, category):
break
+ child.check_position()
else:
child = None
return [] if child is None else [child, *ichildren]
@@ -1194,6 +1201,13 @@
return (f'{msg} Expecting child of type <{type}>, '
f'not {child.starttag()}.')
+ def check_position(self):
+ """Hook for additional checks of the parent's content model.
+
+ Raise ValidationError, if `self` is at an invalid position.
+ See `subtitle.check_position()` and `transition.check_position()`.
+ """
+
def validate(self, recursive=True):
"""Validate Docutils Document Tree element ("doctree").
@@ -1420,9 +1434,17 @@
valid_attributes = Element.valid_attributes + ('auto', 'refid')
-class subtitle(Titular, PreBibliographic, SubStructural, TextElement): pass
+class subtitle(Titular, PreBibliographic, SubStructural, TextElement):
+ """Sub-title of `document`, `section` and `sidebar`."""
+ def check_position(self):
+ """Check position of subtitle: must follow a title."""
+ if self.parent and self.parent.index(self) == 0:
+ raise ValidationError(f'Element {self.parent.starttag()} invalid:'
+ '\n <subtitle> only allowed after <title>.',
+ problematic_element=self)
+
class meta(PreBibliographic, SubStructural, Element):
"""Container for "invisible" bibliographic data, or meta-data."""
valid_attributes = Element.valid_attributes + (
@@ -1453,13 +1475,36 @@
class transition(SubStructural, Element):
- """Transitions are breaks between untitled text parts.
+ """Transitions__ are breaks between untitled text parts.
- A transition may not begin or end a section or document, nor may two
- transitions be immediately adjacent.
+ __ https://docutils.sourceforge.io/docs/ref/doctree.html#transition
"""
+ def check_position(self):
+ """Check additional constraints on `transition` placement.
+ A transition may not begin or end a section or document,
+ nor may two transitions be immediately adjacent.
+ """
+ messages = [f'Element {self.parent.starttag()} invalid:']
+ predecessor = self.previous_sibling()
+ if (predecessor is None # index == 0
+ or isinstance(predecessor, (title, subtitle, meta, decoration))
+ # A transition following these elements still counts as
+ # "at the beginning of a document or section".
+ ):
+ messages.append(
+ '<transition> may not begin a section or document.')
+ if self.parent.index(self) == len(self.parent) - 1:
+ messages.append('<transition> may not end a section or document.')
+ if isinstance(predecessor, transition):
+ messages.append(
+ '<transition> may not directly follow another transition.')
+ if len(messages) > 1:
+ raise ValidationError('\n '.join(messages),
+ problematic_element=self)
+
+
# Structural Elements
# ===================
@@ -1486,11 +1531,15 @@
content_model = ( # ((title, subtitle?)?, (%body.elements; | topic)+)
(title, '?'),
(subtitle, '?'),
- ((topic, Body), '+')) # TODO complex model
+ ((topic, Body), '+'))
+ # "subtitle only after title" is ensured in `subtitle.check_position()`.
class section(Structural, Element):
- """Document section. The main unit of hierarchy."""
+ """Document section__. The main unit of hierarchy.
+
+ __ https://docutils.sourceforge.io/docs/ref/doctree.html#section
+ """
# recursive content model, see below
@@ -1499,7 +1548,8 @@
(subtitle, '?'),
((Body, topic, sidebar, transition), '*'),
((section, transition), '*'),
- ) # TODO complex model
+ )
+# Correct transition placement is ensured in `transition.check_position()`.
# Root Element
@@ -1527,7 +1577,7 @@
((Body, topic, sidebar, transition), '*'),
((section, transition), '*'),
)
- # additional restrictions for `subtitle` and `transition` will be tested
+ # Additional restrictions for `subtitle` and `transition` are tested
# with the respective `check_position()` methods.
def __init__(self, settings, reporter, *args, **kwargs):
Modified: trunk/docutils/test/test_nodes.py
===================================================================
--- trunk/docutils/test/test_nodes.py 2024-06-05 15:54:16 UTC (rev 9731)
+++ trunk/docutils/test/test_nodes.py 2024-06-05 15:54:27 UTC (rev 9732)
@@ -762,8 +762,9 @@
subtitle = nodes.subtitle()
paragraph = nodes.paragraph()
sidebar = nodes.sidebar('', subtitle, paragraph)
- # TODO additional restriction "only after title"
- sidebar.validate_content()
+ with self.assertRaisesRegex(nodes.ValidationError,
+ '<subtitle> only allowed after <title>.'):
+ sidebar.validate_content()
def test_validate_content_transition(self):
"""Test additional constraints on <transition> placement:
@@ -773,8 +774,20 @@
transition = nodes.transition()
paragraph = nodes.paragraph()
section = nodes.section('', nodes.title(), transition, paragraph)
- # TODO: additional restrictions on transition placement.
- section.validate_content()
+ with self.assertRaisesRegex(nodes.ValidationError,
+ '<transition> may not begin a section '):
+ section.validate_content()
+ section = nodes.section('', nodes.title(), paragraph, transition)
+ with self.assertRaisesRegex(nodes.ValidationError,
+ '<transition> may not end a section '):
+ section.validate_content()
+ section = nodes.section('', nodes.title(), paragraph,
+ nodes.transition(), transition)
+ with self.assertRaisesRegex(nodes.ValidationError,
+ 'Element <section> invalid:\n'
+ ' <transition> may not end .*\n'
+ ' <transition> may not directly '):
+ section.validate_content()
class MiscTests(unittest.TestCase):
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|