SF.net SVN: docutils:[9529 ] trunk/docutils/docutils/u tils/math

milde--- via Docutils-checkins <[email protected]>
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 9529
          http://sourceforge.net/p/docutils/code/9529
Author:   milde
Date:     2024-02-01 13:02:51 +0000 (Thu, 01 Feb 2024)
Log Message:
-----------
Next MathML elements revision.

Include non-default `nchildren` attribute in `__repr__()`.
Define `__str__()` for shorter error reports.

Check type of `MathToken(text)` argument, convert numbers to `str` before storing.

Revise wording of error messages, comments, and docstrings.

Modified Paths:
--------------
    trunk/docutils/docutils/utils/math/latex2mathml.py
    trunk/docutils/docutils/utils/math/mathml_elements.py

Modified: trunk/docutils/docutils/utils/math/latex2mathml.py
===================================================================
--- trunk/docutils/docutils/utils/math/latex2mathml.py	2024-02-01 13:02:42 UTC (rev 9528)
+++ trunk/docutils/docutils/utils/math/latex2mathml.py	2024-02-01 13:02:51 UTC (rev 9529)
@@ -610,9 +610,9 @@
 # >>> parse_latex_math(math(), '\\sqrt 2 \\ne 3')
 # math(msqrt(mn('2')), mo('≠'), mn('3'))
 # >>> parse_latex_math(math(), '\\sqrt{2 + 3} < 10')
-# math(msqrt(mn('2'), mo('+'), mn('3')), mo('<'), mn('10'))
+# math(msqrt(mn('2'), mo('+'), mn('3'), nchildren=3), mo('<'), mn('10'))
 # >>> parse_latex_math(math(), '\\sqrt[3]{2 + 3}')
-# math(mroot(mrow(mn('2'), mo('+'), mn('3')), mn('3')))
+# math(mroot(mrow(mn('2'), mo('+'), mn('3'), nchildren=3), mn('3')))
 # >>> parse_latex_math(math(), '\max_x') # function takes limits
 # math(munder(mo('max', movablelimits='true'), mi('x')))
 # >>> parse_latex_math(math(), 'x^j_i') # ensure correct order: base, sub, sup
@@ -1018,11 +1018,13 @@
 # >>> handle_math_alphabet('mathbb', math(), '{R} = 3')
 # (math(mi('ℝ')), ' = 3')
 # >>> handle_math_alphabet('mathcal', math(), '{F = 3}')
-# (math(mrow(mi('ℱ'), mo('='), mn('3'))), '')
-# >>> handle_math_alphabet('mathrm', math(), '{out} = 3')  # use shortcut
+# (math(mrow(mi('ℱ'), mo('='), mn('3'), nchildren=3)), '')
+# >>> handle_math_alphabet('mathrm', math(), '{out} = 3')  # drop <mrow>
 # (math(mi('out')), ' = 3')
-# >>> handle_math_alphabet('mathrm', math(), '{V = 3}')  # force normal
-# (math(mrow(mi('V', mathvariant='normal'), mo('='), mn('3'))), '')
+#
+# Single letters in \mathrm require "mathvariant='normal'":
+# >>> handle_math_alphabet('mathrm', math(), '{V = 3}')  # doctest: +ELLIPSIS
+# (math(mrow(mi('V', mathvariant='normal'), mo('='), mn('3'), ...)), '')
 
 
 def handle_script_or_limit(node, c, limits=''):

Modified: trunk/docutils/docutils/utils/math/mathml_elements.py
===================================================================
--- trunk/docutils/docutils/utils/math/mathml_elements.py	2024-02-01 13:02:42 UTC (rev 9528)
+++ trunk/docutils/docutils/utils/math/mathml_elements.py	2024-02-01 13:02:51 UTC (rev 9529)
@@ -26,6 +26,9 @@
 #
 # >>> from mathml_elements import *
 
+import numbers
+
+
 GLOBAL_ATTRIBUTES = (
     'class',  # space-separated list of element classes
     # 'data-*',  # custom data attributes (see HTML)
@@ -57,7 +60,6 @@
     # cf. https://www.w3.org/TR/MathML3/chapter3.html#id.3.1.3.2
     parent = None
     """Parent node in MathML element tree."""
-    _level = 0  # indentation level (static class variable)
     xml_entities = {
         # for invalid and invisible characters
         ord('<'): '&lt;',
@@ -93,14 +95,25 @@
         return str(v)
 
     def __repr__(self):
+        """Return full string representation."""
         args = [repr(child) for child in self]
         if hasattr(self, 'text'):
             args.append(repr(self.text))
+        if self.nchildren != self.__class__.nchildren:
+            args.append(f'nchildren={self.nchildren}')
         if getattr(self, 'switch', None):
             args.append('switch=True')
         args += [f'{k}={v!r}' for k, v in self.items() if v is not None]
-        return self.__class__.__name__ + '(%s)' % ', '.join(args)
+        return f'{self.__class__.__name__}({", ".join(args)})'
 
+    def __str__(self):
+        """Return concise, informal string representation."""
+        if getattr(self, 'text', ''):
+            args = repr(self.text)
+        else:
+            args = ', '.join(f'{child}' for child in self)
+        return f'{self.__class__.__name__}({args})'
+
 # Emulate dictionary access methods for attributes
 # and list-like interface to the child elements
 # (differs from `docutils.nodes.Element` dict/list interface).
@@ -159,7 +172,11 @@
         (returns first non-full anchestor or None) else return `self`.
         """
         if self.is_full():
-            raise TypeError(f'Element "{self}" already full!')
+            if self.nchildren:
+                status = f'takes only {self.nchildren} children'
+            else:
+                status = 'does not take children'
+            raise TypeError(f'Element "{self}" {status}.')
         self.children.append(element)
         element.parent = self
         if self.is_full():
@@ -182,14 +199,16 @@
         return element
 
     def in_block(self):
-        """Return true, if `self` or a parent has ``display='block'``."""
-        try:
-            return self.get('display') == 'block'
-        except KeyError:
+        """Return True, if `self` or an ancestor has ``display='block'``.
+
+        Used to find out whether we are in inline vs. displayed maths.
+        """
+        if self.get('display') is None:
             try:
                 return self.parent.in_block()
             except AttributeError:
                 return False
+        return self.get('display') == 'block'
 
     # Conversion to (pretty) XML string
     def toprettyxml(self):
@@ -197,9 +216,6 @@
         return ''.join(self._xml())
 
     def _xml(self, level=0):
-        if self.nchildren is not None and len(self) < self.nchildren:
-            raise ValueError(f'Node {self.xml_starttag()} misses children.'
-                             ' Incomplete source?')
         return [self.xml_starttag(),
                 *self._xml_body(level),
                 '</%s>' % self.__class__.__name__]
@@ -218,13 +234,13 @@
 
 # >>> n2 = math(mn(2))
 # >>> n2
-# math(mn(2))
+# math(mn('2'))
 # >>> n2.toprettyxml()
 # '<math>\n  <mn>2</mn>\n</math>'
 # >>> len(n2)
 # 1
 # >>> n2.extend([mo('!')])
-# math(mn(2), mo('!'))
+# math(mn('2'), mo('!'))
 # >>> eq3 = math(id='eq3', display='block')
 # >>> eq3
 # math(id='eq3', display='block')
@@ -290,10 +306,10 @@
 # MathSchema(MathElement(), switch=True)
 # >>> MathSchema(MathElement(id='c1'), MathElement(id='c2'), switch=True)
 # MathSchema(MathElement(id='c2'), MathElement(id='c1'))
-# >>> MathSchema(MathElement(), MathElement(), MathElement())
+# >>> MathSchema(mrow(), mrow(), mrow())  # doctest: +ELLIPSIS
 # Traceback (most recent call last):
 # ...
-# TypeError: Element "MathSchema(MathElement(), MathElement())" already full!
+# TypeError: Element "MathSchema(...)" takes only 2 children.
 
 
 # Token elements represent the smallest units of mathematical notation which
@@ -307,8 +323,11 @@
     nchildren = 0
 
     def __init__(self, text, **attributes):
-        self.text = text
         super().__init__(**attributes)
+        if not isinstance(text, (str, numbers.Number)):
+            raise ValueError('MathToken element expects `str` or number,'
+                             f' not "{text}".')
+        self.text = str(text)
 
     def _xml_body(self, level=0):
         return [str(self.text).translate(self.xml_entities)]
@@ -331,7 +350,12 @@
 class mi(MathToken):
     """Identifier, such as a function name, variable or symbolic constant."""
 
+# >>> mi(mtext('out'))
+# Traceback (most recent call last):
+# ...
+# ValueError: MathToken element expects `str` or number, not "mtext('out')".
 
+
 class mn(MathToken):
     """Numeric literal.
 
@@ -338,7 +362,10 @@
     Normally a sequence of digits with a possible separator (a dot or a comma).
     """
 
+# >>> mn(3.41)
+# mn('3.41')
 
+
 class mo(MathToken):
     """Operator, Fence, Separator, or Accent.
 
@@ -349,8 +376,8 @@
 
 # >>> mo('<')
 # mo('<')
-# >>> mo('<')._xml()
-# ['<mo>', '&lt;', '</mo>']
+# >>> mo('<').toprettyxml()
+# '<mo>&lt;</mo>'
 
 
 class mspace(MathElement):
@@ -363,7 +390,14 @@
     """
     nchildren = 0
 
+# >>> mspace(width='2em').toprettyxml()
+# '<mspace width="2em">\n</mspace>'
+# >>> mspace(mn(3))
+# Traceback (most recent call last):
+# ...
+# TypeError: Element "mspace()" does not take children.
 
+
 # General Layout Schemata
 # ~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -374,20 +408,20 @@
     """
 
     def transfer_attributes(self, other):
-        # Update dictionary `other.attrib` with self.attrib.
-        # List values (class, style) are appended to existing values,
-        # other values replace existing values.
+        """Transfer attributes from self to other.
+
+        List values (class, style) are appended to existing values,
+        other values replace existing values.
+        """
+        delimiters = {'class': ' ', 'style': '; '}
         for k, v in self.items():
             if k in ('class', 'style') and v:
-                try:
-                    other.attrib[k] += ' ' + v
-                    continue
-                except (KeyError, TypeError):
-                    pass
-            other.attrib[k] = v
+                if other.get(k):
+                    v = delimiters[k].join((other.get(k), v))
+            other.set(k, v)
 
     def close(self):
-        """Close element and return first non-full parent or None.
+        """Close element and return first non-full anchestor or None.
 
         Remove <mrow> if it has only one child element.
         """
@@ -403,6 +437,18 @@
             self.transfer_attributes(child)
         return super().close()
 
+# Examples:
+#
+# >>> e = mrow(mn('3.5'), mo('<'), mi('x'))
+# >>> e
+# mrow(mn('3.5'), mo('<'), mi('x'))
+# >>> e.toprettyxml()
+# '<mrow>\n  <mn>3.5</mn>\n  <mo>&lt;</mo>\n  <mi>x</mi>\n</mrow>'
+# >>> mo('∫', movablelimits=True).toprettyxml()
+# '<mo movablelimits="true">∫</mo>'
+# >>> mtext('comments or annotations').text
+# 'comments or annotations'
+
 # >>> row = mrow(mi('i', CLASS='boldmath'), mathvariant='normal', CLASS='test')
 # >>> tree = math(row)
 # >>> row.close()
@@ -465,12 +511,18 @@
     """Attach both a subscript and a superscript to an expression."""
     nchildren = 3
 
-# >>> msub(mi('x'), mo('-'))
-# msub(mi('x'), mo('-'))
-# >>> msubsup(mi('base'), mi('sub'), mi('super'))
-# msubsup(mi('base'), mi('sub'), mi('super'))
-# >>> msubsup(mi('base'), mi('super'), mi('sub'), switch=True)
-# msubsup(mi('base'), mi('sub'), mi('super'))
+# Examples:
+#
+# The `switch` attribute reverses the order of the last two children:
+# >>> msub(mn(1), mn(2)).toprettyxml()
+# '<msub>\n  <mn>1</mn>\n  <mn>2</mn>\n</msub>'
+# >>> msub(mn(1), mn(2), switch=True).toprettyxml()
+# '<msub>\n  <mn>2</mn>\n  <mn>1</mn>\n</msub>'
+#
+# >>> msubsup(mi('base'), mn(1), mn(2)).toprettyxml()
+# '<msubsup>\n  <mi>base</mi>\n  <mn>1</mn>\n  <mn>2</mn>\n</msubsup>'
+# >>> msubsup(mi('base'), mn(1), mn(2), switch=True).toprettyxml()
+# '<msubsup>\n  <mi>base</mi>\n  <mn>2</mn>\n  <mn>1</mn>\n</msubsup>'
 
 
 class munder(msub):
@@ -495,7 +547,7 @@
 # munder(mi('lim'), mo('-'), accent='false')
 # >>> mu.append(mi('lim'))
 # Traceback (most recent call last):
-# TypeError: Element "munder(mi('lim'), mo('-'), accent='false')" already full!
+# TypeError: Element "munder(mi('lim'), mo('-'))" takes only 2 children.
 # >>> munder(mo('-'), mi('lim'), accent='false', switch=True).toprettyxml()
 # '<munder accent="false">\n  <mi>lim</mi>\n  <mo>-</mo>\n</munder>'
 

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.



_______________________________________________
Docutils-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/docutils-checkins
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.