about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-4a52a71956a8d46fcb7294ac71734504bb09bcc2.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py')
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py478
1 files changed, 478 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py
new file mode 100644
index 00000000..f2059c9f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py
@@ -0,0 +1,478 @@
+# :Id: $Id: mathml_elements.py 9561 2024-03-14 16:34:48Z milde $
+# :Copyright: 2024 Günter Milde.
+#
+# :License: Released under the terms of the `2-Clause BSD license`_, in short:
+#
+#    Copying and distribution of this file, with or without modification,
+#    are permitted in any medium without royalty provided the copyright
+#    notice and this notice are preserved.
+#    This file is offered as-is, without any warranty.
+#
+# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
+
+"""MathML element classes based on `xml.etree`.
+
+The module is intended for programmatic generation of MathML
+and covers the part of `MathML Core`_ that is required by
+Docutil's *TeX math to MathML* converter.
+
+This module is PROVISIONAL:
+the API is not settled and may change with any minor Docutils version.
+
+.. _MathML Core: https://www.w3.org/TR/mathml-core/
+"""
+
+# Usage:
+#
+# >>> from mathml_elements import *
+
+import numbers
+import xml.etree.ElementTree as ET
+
+
+GLOBAL_ATTRIBUTES = (
+    'class',  # space-separated list of element classes
+    # 'data-*',  # custom data attributes (see HTML)
+    'dir',  # directionality ('ltr', 'rtl')
+    'displaystyle',  # True: normal, False: compact
+    'id',  # unique identifier
+    # 'mathbackground',  # color definition, deprecated
+    # 'mathcolor',  # color definition, deprecated
+    # 'mathsize',  # font-size, deprecated
+    'nonce',  # cryptographic nonce ("number used once")
+    'scriptlevel',  # math-depth for the element
+    'style',  # CSS styling declarations
+    'tabindex',  # indicate if the element takes input focus
+    )
+"""Global MathML attributes
+
+https://w3c.github.io/mathml-core/#global-attributes
+"""
+
+
+# Base classes
+# ------------
+
+class MathElement(ET.Element):
+    """Base class for MathML elements."""
+
+    nchildren = None
+    """Expected number of children or None"""
+    # cf. https://www.w3.org/TR/MathML3/chapter3.html#id.3.1.3.2
+    parent = None
+    """Parent node in MathML element tree."""
+
+    def __init__(self, *children, **attributes):
+        """Set up node with `children` and `attributes`.
+
+        Attribute names are normalised to lowercase.
+        You may use "CLASS" to set a "class" attribute.
+        Attribute values are converted to strings
+        (with True -> "true" and False -> "false").
+
+        >>> math(CLASS='test', level=3, split=True)
+        math(class='test', level='3', split='true')
+        >>> math(CLASS='test', level=3, split=True).toxml()
+        '<math class="test" level="3" split="true"></math>'
+
+        """
+        attrib = {k.lower(): self.a_str(v) for k, v in attributes.items()}
+        super().__init__(self.__class__.__name__, **attrib)
+        self.extend(children)
+
+    @staticmethod
+    def a_str(v):
+        # Return string representation for attribute value `v`.
+        if isinstance(v, bool):
+            return str(v).lower()
+        return str(v)
+
+    def __repr__(self):
+        """Return full string representation."""
+        args = [repr(child) for child in self]
+        if 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 f'{self.tag}({", ".join(args)})'
+
+    def __str__(self):
+        """Return concise, informal string representation."""
+        if self.text:
+            args = repr(self.text)
+        else:
+            args = ', '.join(f'{child}' for child in self)
+        return f'{self.tag}({args})'
+
+    def set(self, key, value):
+        super().set(key, self.a_str(value))
+
+    def __setitem__(self, key, value):
+        if self.nchildren == 0:
+            raise TypeError(f'Element "{self}" does not take children.')
+        if isinstance(value, MathElement):
+            value.parent = self
+        else:  # value may be an iterable
+            if self.nchildren and len(self) + len(value) > self.nchildren:
+                raise TypeError(f'Element "{self}" takes only {self.nchildren}'
+                                ' children')
+            for e in value:
+                e.parent = self
+        super().__setitem__(key, value)
+
+    def is_full(self):
+        """Return boolean indicating whether children may be appended."""
+        return self.nchildren is not None and len(self) >= self.nchildren
+
+    def close(self):
+        """Close element and return first non-full anchestor or None."""
+        self.nchildren = len(self)  # mark node as full
+        parent = self.parent
+        while parent is not None and parent.is_full():
+            parent = parent.parent
+        return parent
+
+    def append(self, element):
+        """Append `element` and return new "current node" (insertion point).
+
+        Append as child element and set the internal `parent` attribute.
+
+        If self is already full, raise TypeError.
+
+        If self is full after appending, call `self.close()`
+        (returns first non-full anchestor or None) else return `self`.
+        """
+        if self.is_full():
+            if self.nchildren:
+                status = f'takes only {self.nchildren} children'
+            else:
+                status = 'does not take children'
+            raise TypeError(f'Element "{self}" {status}.')
+        super().append(element)
+        element.parent = self
+        if self.is_full():
+            return self.close()
+        return self
+
+    def extend(self, elements):
+        """Sequentially append `elements`. Return new "current node".
+
+        Raise TypeError if overfull.
+        """
+        current_node = self
+        for element in elements:
+            current_node = self.append(element)
+        return current_node
+
+    def pop(self, index=-1):
+        element = self[index]
+        del self[index]
+        return element
+
+    def in_block(self):
+        """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'
+
+    # XML output:
+
+    def indent_xml(self, space='  ', level=0):
+        """Format XML output with indents.
+
+        Use with care:
+          Formatting whitespace is permanently added to the
+          `text` and `tail` attributes of `self` and anchestors!
+        """
+        ET.indent(self, space, level)
+
+    def unindent_xml(self):
+        """Strip whitespace at the end of `text` and `tail` attributes...
+
+        to revert changes made by the `indent_xml()` method.
+        Use with care, trailing whitespace from the original may be lost.
+        """
+        for e in self.iter():
+            if not isinstance(e, MathToken) and e.text:
+                e.text = e.text.rstrip()
+            if e.tail:
+                e.tail = e.tail.rstrip()
+
+    def toxml(self, encoding=None):
+        """Return an XML representation of the element.
+
+        By default, the return value is a `str` instance. With an explicit
+        `encoding` argument, the result is a `bytes` instance in the
+        specified encoding. The XML default encoding is UTF-8, any other
+        encoding must be specified in an XML document header.
+
+        Name and encoding handling match `xml.dom.minidom.Node.toxml()`
+        while `etree.Element.tostring()` returns `bytes` by default.
+        """
+        xml = ET.tostring(self, encoding or 'unicode',
+                          short_empty_elements=False)
+        # Visible representation for "Apply Function" character:
+        try:
+            xml = xml.replace('\u2061', '&ApplyFunction;')
+        except TypeError:
+            xml = xml.replace('\u2061'.encode(encoding), b'&ApplyFunction;')
+        return xml
+
+
+# Group sub-expressions in a horizontal row
+#
+# The elements <msqrt>, <mstyle>, <merror>, <mpadded>, <mphantom>,
+# <menclose>, <mtd>, <mscarry>, and <math> treat their contents
+# as a single inferred mrow formed from all their children.
+# (https://www.w3.org/TR/mathml4/#presm_inferredmrow)
+#
+# MathML Core uses the term "anonymous mrow element".
+
+class MathRow(MathElement):
+    """Base class for elements treating content as a single mrow."""
+
+
+# 2d Schemata
+
+class MathSchema(MathElement):
+    """Base class for schemata expecting 2 or more children.
+
+    The special attribute `switch` indicates that the last two child
+    elements are in reversed order and must be switched before XML-export.
+    See `msub` for an example.
+    """
+    nchildren = 2
+
+    def __init__(self, *children, **kwargs):
+        self.switch = kwargs.pop('switch', False)
+        super().__init__(*children, **kwargs)
+
+    def append(self, element):
+        """Append element. Normalize order and close if full."""
+        current_node = super().append(element)
+        if self.switch and self.is_full():
+            self[-1], self[-2] = self[-2], self[-1]
+            self.switch = False
+        return current_node
+
+
+# Token elements represent the smallest units of mathematical notation which
+# carry meaning.
+
+class MathToken(MathElement):
+    """Token Element: contains textual data instead of children.
+
+    Expect text data on initialisation.
+    """
+    nchildren = 0
+
+    def __init__(self, text, **attributes):
+        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)
+
+
+# MathML element classes
+# ----------------------
+
+class math(MathRow):
+    """Top-level MathML element, a single mathematical formula."""
+
+
+# Token elements
+# ~~~~~~~~~~~~~~
+
+class mtext(MathToken):
+    """Arbitrary text with no notational meaning."""
+
+
+class mi(MathToken):
+    """Identifier, such as a function name, variable or symbolic constant."""
+
+
+class mn(MathToken):
+    """Numeric literal.
+
+    >>> mn(3.41).toxml()
+    '<mn>3.41</mn>'
+
+    Normally a sequence of digits with a possible separator (a dot or a comma).
+    (Values with comma must be specified as `str`.)
+    """
+
+
+class mo(MathToken):
+    """Operator, Fence, Separator, or Accent.
+
+    >>> mo('<').toxml()
+    '<mo>&lt;</mo>'
+
+    Besides operators in strict mathematical meaning, this element also
+    includes "operators" like parentheses, separators like comma and
+    semicolon, or "absolute value" bars.
+    """
+
+
+class mspace(MathElement):
+    """Blank space, whose size is set by its attributes.
+
+    Takes additional attributes `depth`, `height`, `width`.
+    Takes no children and no text.
+
+    See also `mphantom`.
+    """
+    nchildren = 0
+
+
+# General Layout Schemata
+# ~~~~~~~~~~~~~~~~~~~~~~~
+
+class mrow(MathRow):
+    """Generic element to group children as a horizontal row.
+
+    Removed on closing if not required (see `mrow.close()`).
+    """
+
+    def transfer_attributes(self, other):
+        """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:
+                if other.get(k):
+                    v = delimiters[k].join(
+                        (other.get(k).rstrip(delimiters[k]), v))
+            other.set(k, v)
+
+    def close(self):
+        """Close element and return first non-full anchestor or None.
+
+        Remove <mrow> if it has only one child element.
+        """
+        parent = self.parent
+        # replace `self` with single child
+        if parent is not None and len(self) == 1:
+            child = self[0]
+            try:
+                parent[list(parent).index(self)] = child
+                child.parent = parent
+            except (AttributeError, ValueError):
+                return None
+            self.transfer_attributes(child)
+        return super().close()
+
+
+class mfrac(MathSchema):
+    """Fractions or fraction-like objects such as binomial coefficients."""
+
+
+class msqrt(MathRow):
+    """Square root. See also `mroot`."""
+    nchildren = 1  # \sqrt expects one argument or a group
+
+
+class mroot(MathSchema):
+    """Roots with an explicit index. See also `msqrt`."""
+
+
+class mstyle(MathRow):
+    """Style Change.
+
+    In modern browsers, <mstyle> is equivalent to an <mrow> element.
+    However, <mstyle> may still be relevant for compatibility with
+    MathML implementations outside browsers.
+    """
+
+
+class merror(MathRow):
+    """Display contents as error messages."""
+
+
+class menclose(MathRow):
+    """Renders content inside an enclosing notation...
+
+    ... specified by the notation attribute.
+
+    Non-standard but still required by Firefox for boxed expressions.
+    """
+    nchildren = 1  # \boxed expects one argument or a group
+
+
+class mpadded(MathRow):
+    """Adjust space around content."""
+    # nchildren = 1  # currently not used by latex2mathml
+
+
+class mphantom(MathRow):
+    """Placeholder: Rendered invisibly but dimensions are kept."""
+    nchildren = 1  # \phantom expects one argument or a group
+
+
+# Script and Limit Schemata
+# ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class msub(MathSchema):
+    """Attach a subscript to an expression."""
+
+
+class msup(MathSchema):
+    """Attach a superscript to an expression."""
+
+
+class msubsup(MathSchema):
+    """Attach both a subscript and a superscript to an expression."""
+    nchildren = 3
+
+# Examples:
+#
+# The `switch` attribute reverses the order of the last two children:
+# >>> msub(mn(1), mn(2)).toxml()
+# '<msub><mn>1</mn><mn>2</mn></msub>'
+# >>> msub(mn(1), mn(2), switch=True).toxml()
+# '<msub><mn>2</mn><mn>1</mn></msub>'
+#
+# >>> msubsup(mi('base'), mn(1), mn(2)).toxml()
+# '<msubsup><mi>base</mi><mn>1</mn><mn>2</mn></msubsup>'
+# >>> msubsup(mi('base'), mn(1), mn(2), switch=True).toxml()
+# '<msubsup><mi>base</mi><mn>2</mn><mn>1</mn></msubsup>'
+
+
+class munder(msub):
+    """Attach an accent or a limit under an expression."""
+
+
+class mover(msup):
+    """Attach an accent or a limit over an expression."""
+
+
+class munderover(msubsup):
+    """Attach accents or limits both under and over an expression."""
+
+
+# Tabular Math
+# ~~~~~~~~~~~~
+
+class mtable(MathElement):
+    """Table or matrix element."""
+
+
+class mtr(MathRow):
+    """Row in a table or a matrix."""
+
+
+class mtd(MathRow):
+    """Cell in a table or a matrix"""