|
From: <mi...@us...> - 2021-05-27 15:06:42
|
Revision: 8758
http://sourceforge.net/p/docutils/code/8758
Author: milde
Date: 2021-05-27 15:06:37 +0000 (Thu, 27 May 2021)
Log Message:
-----------
MathML: refactor and extend.
New method math.extend(), dictionary-like access to attributes.
Update/Correct list of operators/functions with limits above/below in display formulas.
Add support for:
- more matrix environments,
- more fraction commands,
- delimiter-size commands,
- limits/nolimits commands,
- phantom and boxed commands,
- layout style commands.
Modified Paths:
--------------
trunk/docutils/docs/ref/rst/mathematics.txt
trunk/docutils/docutils/utils/math/latex2mathml.py
trunk/docutils/test/functional/expected/math_output_mathml.html
Modified: trunk/docutils/docs/ref/rst/mathematics.txt
===================================================================
--- trunk/docutils/docs/ref/rst/mathematics.txt 2021-05-20 12:25:30 UTC (rev 8757)
+++ trunk/docutils/docs/ref/rst/mathematics.txt 2021-05-27 15:06:37 UTC (rev 8758)
@@ -6,6 +6,7 @@
.. default-role:: math
.. |latex| replace:: L\ :sup:`A`\ T\ :sub:`E`\ X
+.. sectnum::
.. contents::
Introduction
@@ -248,6 +249,8 @@
.. math:: \operatorname{sgn}(-3) = -1.
+.. TODO: \operatorname* for function name with limits.
+
The ``\DeclareMathOperator`` command can only be used in the
`LaTeX preamble`_.
@@ -358,7 +361,7 @@
mathematical symbol (for other output formats, results are mixed):
.. math::
- \boldsymbol{abs(x) \pm \alpha \approx 3 \Gamma \quad \forall x \in R}
+ \boldsymbol{abs(x)\pm\alpha \approx 3\Gamma \quad \forall x\in\mathbb{R}}
Miscellaneous symbols
@@ -523,19 +526,19 @@
.. class:: colwidths-auto
========================= ========================= ========================= ===========================
- `\sum` ``\sum`` `\prod` ``\prod`` `\bigcap` ``\bigcap`` `\bigodot` ``\bigodot``
- `\int` ``\int`` `\coprod` ``\coprod`` `\bigcup` ``\bigcup`` `\bigoplus` ``\bigoplus``
+ `\sum` ``\sum`` `\prod` ``\prod`` `\bigcap` ``\bigcap`` `\bigodot` ``\bigodot``
+ `\int` ``\int`` `\coprod` ``\coprod`` `\bigcup` ``\bigcup`` `\bigoplus` ``\bigoplus``
`\oint` ``\oint`` `\bigwedge` ``\bigwedge`` `\biguplus` ``\biguplus`` `\bigotimes` ``\bigotimes``
- `\smallint` ``\smallint`` `\bigvee` ``\bigvee`` `\bigsqcup` ``\bigsqcup``
+ `\smallint` ``\smallint`` `\bigvee` ``\bigvee`` `\bigsqcup` ``\bigsqcup``
========================= ========================= ========================= ===========================
-Larger symbols are used in displayed formulas:
+Larger symbols are used in displayed formulas, sum-like symbols have
+indices above/below the symbol (see also `scripts and limits`_):
-.. math::
+.. math:: \sum_{n=1}^N a_n \qquad
+ \int_0^1f(x)\,dx \qquad
+ \prod_{i=1}^{10} b_i \ldots
- \sum\ \int\ \oint \bigcap \prod \ldots
-
-
Notations
=========
@@ -574,54 +577,74 @@
``&``::
.. math::
+ \left(\begin{matrix} a & b \\ c & d \end{matrix}\right)
- \left(\begin{matrix} a & b \\ c & d \end{matrix}\right)
-
Result:
.. math::
+ \left(\begin{matrix} a & b \\ c & d \end{matrix}\right)
- \left(\begin{matrix} a & b \\ c & d \end{matrix}\right)
+The environments ``pmatrix``, ``bmatrix``, ``Bmatrix``, ``vmatrix``, and
+``Vmatrix`` have (respectively) ( ), [ ], { }, \| \|, and `\Vert\ \Vert`
+delimiters built in, e.g.
-.. TODO: small matrices, matrices with delimiters built in?
+.. math:: \begin{pmatrix} a & b \\ c & d \end{pmatrix} \qquad
+ \begin{bmatrix} a & b \\ c & d \end{bmatrix} \qquad
+ \begin{Vmatrix} a & b \\ c & d \end{Vmatrix}
- The environments pmatrix, bmatrix, Bmatrix, vmatrix, and Vmatrix have
- (respectively) ( ), [ ], { }, | |, and k k delimiters built in.
+To produce a small matrix suitable for use in text, there is a
+``smallmatrix`` environment
+`\bigl(\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}\bigr)`
+that comes closer to fitting within a single text line than a normal
+matrix.
-For piecewise function definitions there is a cases environment:
-.. math::
+For piecewise function definitions there is a ``cases`` environment:
- \mathrm{sgn}(x) = \begin{cases}
- -1 & x<0\\
- 1 & x>0
- \end{cases}
+.. math:: \mathrm{sgn}(x) = \begin{cases}
+ -1 & x<0\\
+ 1 & x>0
+ \end{cases}
-Horizontal space
+Spacing commands
----------------
+Horizontal spacing of elements can be controled with the following
+commands:
+
.. class:: colwidths-auto
===================== ======== =======================
- :m:`|\qquad|` ``|\qquad|`` (2em)
- :m:`|\quad|` ``|\quad|`` (1em)
- :m:`|~|` ``|~|`` ``|\nobreakspace|``
- :m:`|\ |` ``|\ |`` escaped space
- :m:`|\;|` ``|\;|`` ``|\thickspace|``
- :m:`|\:|` ``|\:|`` ``|\medspace|``
- :m:`|\,|` ``|\,|`` ``|\thinspace|``
- :m:`| |` ``| |`` regular space (ignored)
- :m:`|\!|` ``|\!|`` ``|\negthinspace|``
- :m:`|\negmedspace|` ``|\negmedspace|``
- :m:`|\negthickspace|` ``|\negthickspace|``
- `|\hspace{1ex}|` ``|\hspace{1ex}|``
+ :m:`3\qquad8` ``3\qquad8`` (2em)
+ :m:`3\quad8` ``3\quad8`` (1em)
+ :m:`3~8` ``3~8`` ``3\nobreakspace8``
+ :m:`3\ 8` ``3\ 8`` escaped space
+ :m:`3\;8` ``3\;8`` ``3\thickspace8``
+ :m:`3\:8` ``3\:8`` ``3\medspace8``
+ :m:`3\,8` ``3\,8`` ``3\thinspace8``
+ :m:`3 8` ``3 8`` regular space (ignored)
+ :m:`3\!8` ``3\!8`` ``3\negthinspace8``
+ :m:`3\negmedspace8` ``3\negmedspace8``
+ :m:`3\negthickspace8` ``3\negthickspace8``
+ `3\hspace{1ex}8` ``3\hspace{1ex}8``
===================== ======== =======================
-.. TODO: \phantom \hphantom, \vphantom
+Whitespace is ignored in LaTeX math mode.
+Negative spacing does not work with MathML (in Firefox 78).
-Roots and Fractions
--------------------
+There are also three commands that leave a space equal to the height and
+width of its argument. For example ``\phantom{XXX}`` results in space as
+wide and high as three X’s:
+.. math:: \frac{\phantom{XXX}+1}{XXX-1}
+
+The commands ``\hphantom`` and ``\vphantom`` insert space with the heigth
+rsp. width of the argument. They are not supported with `math_output`_
+MathML.
+
+Roots
+-----
+
.. class:: colwidths-auto
========= ==================== ==================
@@ -629,11 +652,116 @@
========= ==================== ==================
``\sqrt`` ``\sqrt{x^2-1}`` `\sqrt{x^2-1}`
.. ``\sqrt[3n]{x^2-1}`` `\sqrt[3n]{x^2-1}`
- ``\frac`` ``\frac{1}{1-x}`` `\frac{1}{1-x}`
+ .. ``\sqrt\frac{1}{2}`` `\sqrt\frac{1}{2}`
========= ==================== ==================
-.. TODO: \dfrac, \tfrac, \binom, \dbinom, \tbinom
+Boxed formulas
+--------------
+The command ``\boxed`` puts a box around its argument:
+
+.. math:: \boxed{\eta \leq C(\delta(\eta) +\Lambda_M(0,\delta))}
+
+
+
+Fractions and related constructions
+===================================
+
+The ``\frac`` command takes two ar guments, numerator and denominator,
+and typesets them in normal fraction form. Use ``\dfrac`` or ``\tfrac`` to
+force text style and display style respectively.
+
+.. math:: \frac{x+1}{x-1} \quad
+ \dfrac{x+1}{x-1} \quad
+ \tfrac{x+1}{x-1}
+
+and in text: `\frac{x+1}{x-1}`, `\dfrac{x+1}{x-1}`, `\tfrac{x+1}{x-1}`.
+
+For binomial expressions such as `\binom{n}{k}`,
+there are ``\binom``, ``\dbinom`` and ``\tbinom`` commands::
+
+ 2^k-\binom{k}{1}2^{k-1}+\binom{k}{2}2^{k-2}
+
+prints
+
+.. math:: 2^k-\binom{k}{1}2^{k-1}+\binom{k}{2}2^{k-2}
+
+The ``\cfrac`` command for continued fractions uses displaystyle and
+padding for sub-fractions:
+
+.. math:: \cfrac{1}{\sqrt{2}+
+ \cfrac{1}{\sqrt{2}+
+ \cfrac{1}{\sqrt{2}+\dotsb
+ }}}
+ \text{ vs. }
+ \frac{1}{\sqrt{2}+
+ \frac{1}{\sqrt{2}+
+ \frac{1}{\sqrt{2}+\dotsb
+ }}}
+
+It supports the optional argument ``[l]`` or ``[r]`` for
+left or right placement of the numerator:
+
+.. math:: \cfrac[l]{x}{x-1} \quad
+ \cfrac{x}{x-1} \quad
+ \cfrac[r]{x}{x-1}
+
+
+Delimiter sizes
+===============
+
+Besides the automatic scaling of `extensible delimiters`_ with ``\left``
+and ``\right``, there are four commands to manually select delimiters of
+fixed size:
+
+.. class:: colwidths-auto
+
+ ========= ============== ============== ============== ============== =============== ===============
+ Sizing no ``\left`` ``\bigl`` ``\Bigl`` ``\biggl`` ``\Biggl``
+ command ``\right`` ``\bigr`` ``\Bigr`` ``\biggr`` ``\Biggr``
+ --------- -------------- -------------- -------------- -------------- --------------- ---------------
+ Result `\displaystyle `\displaystyle `\displaystyle `\displaystyle `\displaystyle `\displaystyle
+ (b) \left(b\right) \bigl(b\bigr) \Bigl(b\Bigr) \biggl(b\biggr) \Biggl(b\Biggr)
+ (\frac{c}{d})` \left(\frac{c} \bigl(\frac{c} \Bigl(\frac{c} \biggl(\frac{c} \Biggl(\frac{c}
+ {d}\right)` {d}\bigr)` {d}\Bigr)` {d}\biggr)` {d}\Biggr)`
+ ========= ============== ============== ============== ============== =============== ===============
+
+There are two or three situations where the delimiter size is commonly
+adjusted using these commands:
+
+The first kind of adjustment is done for cumulative operators with
+limits, such as summation signs. With ``\left`` and ``\right`` the
+delimiters usually turn out larger than necessary, and using the ``Big``
+or ``bigg`` sizes instead gives better results:
+
+.. math::
+ \left[\sum_i a_i\left\lvert\sum_j x_{ij}\right\rvert^p\right]^{1/p}
+ \text{ versus }
+ \biggl[\sum_i a_i\Bigl\lvert\sum_j x_{ij}\Bigr\rvert^p\biggr]^{1/p}
+
+The second kind of situation is clustered pairs of delimiters, where
+\left and \right make them all the same size (because that is adequate to
+cover the encompassed material), but what you really want is to make some
+of the delimiters slightly larger to make the nesting easier to see.
+
+.. math:: \left((a_1 b_1) - (a_2 b_2)\right)
+ \left((a_2 b_1) + (a_1 b_2)\right)
+ \quad\text{versus}\quad
+ \bigl((a_1 b_1) - (a_2 b_2)\bigr)
+ \bigl((a_2 b_1) + (a_1 b_2)\bigr)
+
+The third kind of situation is a slightly oversize object in running
+text, such as `\left|\frac{b'}{d'}\right|` where the delimiters produced
+by ``\left`` and ``\right`` cause too much line spreading. [#]_ In that case
+``\bigl`` and ``\bigr`` can be used to produce delimiters that are larger
+than the base size but still able to fit within the normal line spacing:
+`\bigl|\frac{b'}{d'}\bigr|`.
+
+.. [#] With MathML, an example would be parentheses
+ around a ``smallmatrix`` environment
+ `\left(\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}\right)`
+ vs. `\Bigl(\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}\Bigr)`.
+
Text
====
@@ -647,14 +775,93 @@
.. Math:: f_{[x_{i-1},x_i]} \text{ is monotonic for } i = 1,\,…,\,c+1
-Currently, math in text is not supported by LaTeX2MathML.
+``$`` signs can be used for math commands inside the text, e.g.
-.. TODO: Math inside text: ``n - 1 \text{if $n$ is odd}``.
+.. math:: (-1)^n = \begin{cases} -1 \quad \text{if $n$ is odd} \\
+ +1 \quad \text{if $n$ is even.}
+ \end{cases}
+.. TODO: ``\mod`` and its relatives
+ --------------------------
-Tests
-=====
+ Commands ``\mod``, ``\bmod``, ``\pmod``, ``\pod`` deal with the special
+ spacing conventions of “mod” notation. ``\mod`` and ``\pod`` are
+ variants of ``\pmod`` preferred by some authors; ``\mod`` omits the
+ parentheses, whereas ``\pod`` omits the “mod” and retains the
+ parentheses.
+ \gcd(n,m\bmod n) ;\quad x\equiv y\pmod b
+ ;\quad x\equiv y\mod c ;\quad x\equiv y\pod d
+
+
+Integrals and sums
+==================
+
+The limits on integrals, sums, and similar symbols are placed either to
+the side of or above and below the base symbol, depending on convention
+and context. In inline formulas and fractions, the limits on sums, and
+similar symbols like
+
+.. math:: \lim_{n\to\infty} \sum_1^n \frac{1}{n}
+
+move to index postions: `\lim_{n\to\infty} \sum_1^n \frac{1}{n}`.
+
+The commands ``\limits`` and ``\nolimits`` override the automatic
+placement of the limits; ``\displaylimits`` means to use standard
+positioning as for the \sum command. They should follow immediately after
+the operator to which they apply.
+
+Compare the same term with default positions, ``\limits``, and
+``\nolimits`` in inline and display mode: `\lim_{x\to0}f(x)`,
+`\lim\limits_{x\to0}f(x)`, `\lim\nolimits_{x\to0}f(x)`, vs.
+
+.. math:: \lim_{x\to0}f(x), \quad
+ \lim\limits_{x\to0}f(x) \quad
+ \lim\nolimits_{x\to0}f(x).
+
+.. TODO: \substack
+
+.. TODO: \sideset
+
+
+Changing the size of elements in a formula
+==========================================
+
+The declarations [#]_ ``\displaystyle``, ``\textstyle``,
+``\scriptstyle``, and ``\scriptscriptstyle``, select a symbol size and
+spacing that would be applied in (respectively) display math, inline
+math, first-order subscript, or second-order subscript, even when the
+current context would normally yield some other size.
+
+For example ``:math:`\displaystyle \sum_{n=0}^\infty
+\frac{1}{n}``` is printed as `\displaystyle \sum_{n=0}^\infty \frac{1}{n}`
+rather than `\sum_{n=0}^\infty \frac{1}{n}` and ::
+
+ \frac{\scriptstyle\sum_{n > 0} z^n}
+ {\displaystyle\prod_{1\leq k\leq n} (1-q^k)}
+
+yields
+
+.. math::
+
+ \frac{\scriptstyle\sum_{n > 0} z^n}
+ {\displaystyle\prod_{1\leq k\leq n} (1-q^k)}
+ \text{ instead of the default }
+ \frac{\sum_{n > 0} z^n}
+ {\prod_{1\leq k\leq n} (1-q^k)}.
+
+.. [#] "Declarations" are commands that affect processing of the current
+ "group". In particular, notice where the braces fall that delimit the
+ effect of the command: Right: ``{\displaystyle ...}`` Wrong:
+ ``\displaystyle{...}``.
+
+ With math_output_ MathML, the declaration must be the first element
+ after the opening bracket.
+
+
+Appendix: Tests
+===============
+
Font changes
------------
@@ -690,15 +897,9 @@
Accents should be nearer to the base (in MathML Firefox 78, it's vice versa!):
`\bar a \overline a, \bar l \overline l, \bar i \overline i`.
-In inline formulas and fractions, the limits on lim, sum and integrals,
-
-.. math:: \lim_{c\to0} \oint_c f(x) \mathrm{d}x,
-
-should move to index postions: `\lim_{c\to0} \oint_c f(x) \mathrm{d}x`.
-
Sub- and superscript may be given in any order:
`x_i^j = x^j_i` and `\int_0^1 = \int^1_0`.
-Double exponent: `x^{10}^4`, `r_T_\mathrm{in}` and `x_i^n^2`.
+Double exponent: `x^{10^4}`, `r_{T_\mathrm{in}}` and `x_i^{n^2}`.
Binary vs. unary minus operator: `a - b = -c`
Modified: trunk/docutils/docutils/utils/math/latex2mathml.py
===================================================================
--- trunk/docutils/docutils/utils/math/latex2mathml.py 2021-05-20 12:25:30 UTC (rev 8757)
+++ trunk/docutils/docutils/utils/math/latex2mathml.py 2021-05-27 15:06:37 UTC (rev 8758)
@@ -57,24 +57,28 @@
'Gamma':u'\u0393', 'Lambda':u'\u039b'}
# functions -> <mi>
-functions = dict((name, name) for name in
- ('arccos', 'arcsin', 'arctan', 'arg', 'cos', 'cosh',
- 'cot', 'coth', 'csc', 'deg', 'det', 'dim',
- 'exp', 'gcd', 'hom', 'inf', 'ker', 'lg',
- 'ln', 'log', 'max', 'min', 'Pr',
- 'sec', 'sin', 'sinh', 'sup', 'tan', 'tanh'))
-functions.update({# functions with a space in the name
- 'liminf': u'lim\u202finf', 'limsup': u'lim\u202fsup',
- 'injlim': u'inj\u202flim', 'projlim': u'proj\u202flim',
- # embellished function names (see handle_cmdname() below)
- 'varlimsup': 'lim', 'varliminf': 'lim',
- 'varprojlim': 'lim', 'varinjlim': 'lim',
- # custom function name
- 'operatorname': None})
+functions = {# functions with a space in the name
+ 'liminf': u'lim\u202finf',
+ 'limsup': u'lim\u202fsup',
+ 'injlim': u'inj\u202flim',
+ 'projlim': u'proj\u202flim',
+ # embellished function names (see handle_cmd() below)
+ 'varlimsup': 'lim',
+ 'varliminf': 'lim',
+ 'varprojlim': 'lim',
+ 'varinjlim': 'lim',
+ # custom function name
+ 'operatorname': None,
+ }
+functions.update((name, name) for name in
+ ('arccos', 'arcsin', 'arctan', 'arg', 'cos',
+ 'cosh', 'cot', 'coth', 'csc', 'deg',
+ 'det', 'dim', 'exp', 'gcd', 'hom',
+ 'ker', 'lg', 'ln', 'log', 'Pr',
+ 'sec', 'sin', 'sinh', 'tan', 'tanh'))
+# Function with limits: 'lim', 'sup', 'inf', 'max', 'min':
+# use <mo> to allow "movablelimits" attribute (see below).
-# function with limits, use <mo> to allow "movablelimits" attribute
-functions_with_limits = dict((name, name) for name in
- ('lim', 'sup', 'inf', 'max', 'min'))
# math font selection -> <mi mathvariant=...> or <mstyle mathvariant=...>
math_alphabets = {# 'cmdname': 'mathvariant value' # package
@@ -98,6 +102,15 @@
# operator, fence, or separator -> <mo>
+
+math_fences = {# mathfence aliases with adapted spacing
+ 'lvert': u'|', # left |
+ 'lVert': u'\u2016', # left ‖
+ 'rvert': u'|', # right |
+ 'rVert': u'\u2016', # right ‖
+ 'Arrowvert': u'\u2016', # ‖
+ }
+
operators = tex2unichar.mathbin # Binary symbols
operators.update(tex2unichar.mathrel) # Relation symbols, arrow symbols
operators.update(tex2unichar.mathord) # Miscellaneous symbols
@@ -105,7 +118,7 @@
operators.update(tex2unichar.mathopen) # Braces
operators.update(tex2unichar.mathclose) # Braces
operators.update(tex2unichar.mathfence)
-operators.update(functions_with_limits)
+operators.update(math_fences)
operators.update({# negated symbols without pre-composed Unicode character
'nleqq': u'\u2266\u0338', # ≦̸
'ngeqq': u'\u2267\u0338', # ≧̸
@@ -114,16 +127,17 @@
'nsubseteqq': u'\u2AC5\u0338', # ⫅̸
'nsupseteqq': u'\u2AC6\u0338', # ⫆̸
# alias commands:
- 'lvert': u'|', # left |
- 'lVert': u'\u2016', # left ‖
- 'rvert': u'|', # right |
- 'rVert': u'\u2016', # right ‖
- 'Arrowvert': u'\u2016', # ‖
- 'dotsb': u'\u22ef', # ⋯ with binary operators/relations
- 'dotsc': u'\u2026', # … with commas
- 'dotsi': u'\u22ef', # ⋯ with integrals
- 'dotsm': u'\u22ef', # ⋯ multiplication dots
- 'dotso': u'\u2026', # … other dots
+ 'dotsb': u'\u22ef', # ⋯ with binary operators/relations
+ 'dotsc': u'\u2026', # … with commas
+ 'dotsi': u'\u22ef', # ⋯ with integrals
+ 'dotsm': u'\u22ef', # ⋯ multiplication dots
+ 'dotso': u'\u2026', # … other dots
+ # functions with movable limits (requires <mo>)
+ 'lim': 'lim',
+ 'sup': 'sup',
+ 'inf': 'inf',
+ 'max': 'max',
+ 'min': 'min',
})
# special cases
@@ -143,14 +157,23 @@
'smallint': u'\u222b', # ∫ INTEGRAL
}
-# Operators and functions with limits
-# above/below in display formulas and in index position inline
-with_limits = [operators[name] for name in
- ('coprod', 'fatsemi', 'fint', 'iiiint', 'iiint',
- 'iint', 'int', 'oiint', 'oint', 'ointctrclockwise',
- 'prod', 'sqint', 'sum', 'varointclockwise',
- 'lim', 'sup', 'inf', 'max', 'min')]
+# Operators and functions with limits above/below in display formulas
+# and in index position inline (movablelimits="true")
+displaylimits = [operators[name] for name in
+ ('bigcap', 'bigcup', 'bigodot', 'bigoplus', 'bigotimes',
+ 'bigsqcup', 'biguplus', 'bigvee', 'bigwedge',
+ 'coprod', 'prod', 'sum',
+ 'lim', 'max', 'min', 'sup', 'inf')]
+# Depending on settings, integrals may also be in this category.
+# (e.g. if "amsmath" is loaded with option "intlimits", see
+# http://mirror.ctan.org/macros/latex/required/amsmath/amsldoc.pdf)
+# displaylimits.extend(('fint', 'iiiint', 'iiint', 'iint', 'int', 'oiint',
+# 'oint', 'ointctrclockwise', 'sqint',
+# 'varointclockwise',))
+# >>> print(' '.join(displaylimits))
+# ⋂ ⋃ ⨀ ⨁ ⨂ ⨆ ⨄ ⋁ ⋀ ∐ ∏ ∑ lim max min sup inf
+
# pre-composed characters for negated symbols
# see https://www.w3.org/TR/xml-entity-names/#combining
negatables = {'=': u'\u2260',
@@ -158,36 +181,21 @@
r'\equiv': u'\u2262'}
# extensible delimiters allowed in left/right cmds
-stretchables = {'(': '(',
- ')': ')',
- '[': '[',
- ']': ']',
- '/': '/',
- r'\backslash': '\\',
- '|': '|',
- '.': '', # emty fence
- r'\uparrow': u'\u2191', # ↑ UPWARDS ARROW
- r'\downarrow': u'\u2193', # ↓ DOWNWARDS ARROW
- r'\updownarrow': u'\u2195', # ↕ UP DOWN ARROW
- r'\Uparrow': u'\u21d1', # ⇑ UPWARDS DOUBLE ARROW
- r'\Downarrow': u'\u21d3', # ⇓ DOWNWARDS DOUBLE ARROW
- r'\Updownarrow': u'\u21d5', # ⇕ UP DOWN DOUBLE ARROW
- }
-for (key, value) in tex2unichar.mathfence.items():
- stretchables['\\'+key] = value
-for (key, value) in tex2unichar.mathopen.items():
- stretchables['\\'+key] = value
-for (key, value) in tex2unichar.mathclose.items():
- stretchables['\\'+key] = value
-# shorter with {**something} syntax, new in 3.5
-# if sys.version_info >= (3, 5):
-# for (key, value) in {**tex2unichar.mathclose,
-# **tex2unichar.mathopen,
-# **tex2unichar.mathfence}.items():
-# stretchables['\\'+key] = value
+stretchables = {'backslash': '\\',
+ 'uparrow': u'\u2191', # ↑ UPWARDS ARROW
+ 'downarrow': u'\u2193', # ↓ DOWNWARDS ARROW
+ 'updownarrow': u'\u2195', # ↕ UP DOWN ARROW
+ 'Uparrow': u'\u21d1', # ⇑ UPWARDS DOUBLE ARROW
+ 'Downarrow': u'\u21d3', # ⇓ DOWNWARDS DOUBLE ARROW
+ 'Updownarrow': u'\u21d5', # ⇕ UP DOWN DOUBLE ARROW
+ }
+stretchables.update(tex2unichar.mathfence)
+stretchables.update(tex2unichar.mathopen)
+stretchables.update(tex2unichar.mathclose)
+stretchables.update(math_fences)
# >>> print(' '.join(sorted(set(stretchables.values()))))
-# ( ) / [ \ ] { | } ‖ ↑ ↓ ↕ ⇑ ⇓ ⇕ ⌈ ⌉ ⌊ ⌋ ⌜ ⌝ ⌞ ⌟ ⟅ ⟆ ⟦ ⟧ ⟨ ⟩ ⟮ ⟯ ⦇ ⦈
+# [ \ ] { | } ‖ ↑ ↓ ↕ ⇑ ⇓ ⇕ ⌈ ⌉ ⌊ ⌋ ⌜ ⌝ ⌞ ⌟ ⟅ ⟆ ⟦ ⟧ ⟨ ⟩ ⟮ ⟯ ⦇ ⦈
# horizontal space -> <mspace>
@@ -258,6 +266,47 @@
}
+matrices = {'matrix': ('', ''),
+ 'smallmatrix': ('', ''), # smaller, see begin_environment()!
+ 'pmatrix': ('(', ')'),
+ 'bmatrix': ('[', ']'),
+ 'Bmatrix': ('{', '}'),
+ 'vmatrix': ('|', '|'),
+ 'Vmatrix': (u'\u2016', u'\u2016'), # ‖
+ 'cases': ('{', ''),
+ }
+
+layout_styles = {
+ 'displaystyle': {'displaystyle': 'true', 'scriptlevel': '0'},
+ 'textstyle': {'displaystyle': 'false', 'scriptlevel': '0'},
+ 'scriptstyle': {'displaystyle': 'false', 'scriptlevel': '1'},
+ 'scriptscriptstyle': {'displaystyle': 'false', 'scriptlevel': '2'},
+ }
+# See also https://www.w3.org/TR/MathML3/chapter3.html#presm.scriptlevel
+
+fractions = {# name: style_attrs, frac_attrs
+ 'frac': ({}, {}),
+ 'cfrac': ({'displaystyle': 'true', 'scriptlevel': '0',
+ 'CLASS': 'cfrac'}, {}), # in LaTeX with padding
+ 'dfrac': (layout_styles['displaystyle'], {}),
+ 'tfrac': (layout_styles['textstyle'], {}),
+ 'binom': ({}, {'linethickness': 0}),
+ 'dbinom': (layout_styles['displaystyle'], {'linethickness': 0}),
+ 'tbinom': (layout_styles['textstyle'], {'linethickness': 0}),
+ }
+
+delimiter_sizes = {'left': '',
+ 'right': '',
+ 'bigl': '1.2em',
+ 'bigr': '1.2em',
+ 'Bigl': '1.623em',
+ 'Bigr': '1.623em',
+ 'biggl': '2.047em',
+ 'biggr': '2.047em',
+ 'Biggl': '2.470em',
+ 'Biggr': '2.470em',
+ }
+
# MathML element classes
# ----------------------
@@ -282,8 +331,7 @@
"""
self.children = []
- for child in children:
- self.append(child)
+ self.extend(children)
self.attributes = collections.OrderedDict()
# sort attributes for predictable functional tests
@@ -295,7 +343,7 @@
content = [repr(item) for item in getattr(self, 'children', [])]
if hasattr(self, 'data'):
content.append(repr(self.data))
- if isinstance(self, MathScriptOrLimit) and self.switch:
+ if isinstance(self, MathSchema) and self.switch:
content.append('switch=True')
if hasattr(self, 'attributes'):
content += ["%s='%s'"%(k, v) for k, v in self.attributes.items()]
@@ -304,6 +352,13 @@
def __len__(self):
return len(self.children)
+ # emulate dictionary-like access to attributes
+ # see `docutils.nodes.Element` for dict/list interface
+ def __getitem__(self, key):
+ return self.attributes[key]
+ def __setitem__(self, key, item):
+ self.attributes[key] = item
+
def full(self):
"""Return boolean indicating whether children may be appended."""
return (self.nchildren is not None
@@ -323,6 +378,11 @@
return self.close()
return self
+ def extend(self, children):
+ for child in children:
+ self.append(child)
+ return self
+
def close(self):
"""Close element and return first non-full parent or None."""
parent = self.parent
@@ -383,17 +443,20 @@
def close(self):
"""Close element and return first non-full parent or None.
- Remove <mrow>, if it is single child and the parent inferres an mrow
+ Remove <mrow>, if it is single child and the parent infers an mrow
or if it has only one child element.
"""
parent = self.parent
- if isinstance(parent, MathRowInferred) and parent.nchildren == 1:
+ if isinstance(parent, MathRowSchema) and parent.nchildren == 1:
parent.nchildren = None
parent.children = self.children
+ for child in self.children:
+ child.parent = parent
return parent.close()
if len(self) == 1:
try:
parent.children[parent.children.index(self)] = self.children[0]
+ self.children[0].parent = parent
except (AttributeError, ValueError):
return self.children[0]
return super(mrow, self).close()
@@ -404,17 +467,21 @@
# 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.
-class MathRowInferred(math):
+class MathRowSchema(math):
"""Base class for elements treating content as a single inferred mrow."""
-class mtr(MathRowInferred): pass
-class mtd(MathRowInferred): pass
-class mstyle(MathRowInferred):
- nchildren = 1
-class msqrt(MathRowInferred):
- nchildren = 1
+class mtr(MathRowSchema): pass
+class mtd(MathRowSchema): pass
+class mphantom(MathRowSchema):
+ nchildren = 1 # \phantom expects one argument or a group
+class mstyle(MathRowSchema):
+ nchildren = 1 # \mathrm, ... expect one argument or a group
+class msqrt(MathRowSchema):
+ nchildren = 1 # \sqrt expects one argument or a group
+class menclose(MathRowSchema):
+ nchildren = 1 # \boxed expects one argument or a group
class MathToken(math):
- """Token Element: contains data instead of children.
+ """Token Element: contains textual data instead of children.
Base class for mo, mi, and mn.
"""
@@ -437,22 +504,21 @@
# >>> mo(u'<')._xml()
# ['<mo>', '<', '</mo>']
-class MathScriptOrLimit(math):
- """Base class for script and limit schemata."""
+class MathSchema(math):
+ """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.
+ """
+
nchildren = 2
def __init__(self, *children, **kwargs):
- """Set up sub/superscript or limit elements.
-
- The special attribute `switch` tells that the
- last two child elements are in reversed order
- and must be switched before XML-export.
- """
self.switch = kwargs.pop('switch', False)
math.__init__(self, *children, **kwargs)
def append(self, child):
- new_current = super(MathScriptOrLimit, self).append(child)
+ new_current = super(MathSchema, self).append(child)
# normalize order if full
if self.switch and self.full():
self.children[-1], self.children[-2] = self.children[-2], self.children[-1]
@@ -460,14 +526,14 @@
self.switch = False
return new_current
-class msub(MathScriptOrLimit): pass
-class msup(MathScriptOrLimit): pass
-class msubsup(MathScriptOrLimit):
+class msub(MathSchema): pass
+class msup(MathSchema): pass
+class msubsup(MathSchema):
nchildren = 3
-class munder(MathScriptOrLimit): pass
-class mover(MathScriptOrLimit): pass
-class munderover(MathScriptOrLimit):
+class munder(MathSchema): pass
+class mover(MathSchema): pass
+class munderover(MathSchema):
nchildren = 3
# >>> munder(mi('lim'), mo('-'), accent='false')
@@ -490,7 +556,7 @@
# >>> msubsup(mi('base'), mi('super'), mi('sub'), switch=True)
# msubsup(mi('base'), mi('sub'), mi('super'))
-class mroot(MathScriptOrLimit):
+class mroot(MathSchema):
nchildren = 2
class mfrac(math):
@@ -624,7 +690,7 @@
raise SyntaxError('Could not extract optional argument from %r' % string)
# Test:
-# >>> tex_optarg('[optional argument] after whitespace')
+# >>> tex_optarg(' [optional argument] after whitespace')
# ('optional argument', ' after whitespace')
# >>> tex_optarg('[missing right bracket')
# Traceback (most recent call last):
@@ -634,22 +700,18 @@
# SyntaxError: Could not extract optional argument from '[group with [nested group]]'
-
-
def parse_latex_math(node, string):
- """Append MathML conversion of `string` to `node`.
+ """Append MathML conversion of `string` to `node` and return it.
- Return current node.
-
>>> parse_latex_math(math(), r'\alpha')
math(mi('α'))
- >>> parse_latex_math(math(), r'{')
- mrow()
+ >>> parse_latex_math(mrow(), r'x_{n}')
+ mrow(msub(mi('x'), mi('n')))
- Set `inline` to False for displayed math.
"""
# Normalize white-space:
string = ' '.join(string.split())
+ tree = node
while len(string) > 0:
# Take of first character:
@@ -659,7 +721,7 @@
continue # whitespace is ignored in LaTeX math mode
if c == '\\': # start of a LaTeX macro
cmdname, string = tex_cmdname(string)
- node, string = handle_cmdname(cmdname, node, string)
+ node, string = handle_cmd(cmdname, node, string)
elif c in "_^":
node = handle_script_or_limit(node, c)
elif c == '{':
@@ -680,6 +742,8 @@
elif c in anomalous_chars:
# characters with a special meaning in LaTeX math mode
node = node.append(mo(anomalous_chars[c]))
+ # TODO: fix spacing before "unary" minus.
+ # set form='prefix' if preceded by "(", "{", ...?
elif c in "/()[]|":
node = node.append(mo(c, stretchy='false'))
elif c in "+*=<>,.!?';@":
@@ -686,12 +750,12 @@
node = node.append(mo(c))
else:
raise SyntaxError(u'Unsupported character: "%s"' % c)
- return node
+ return tree
# Test:
-# >>> parse_latex_math(math(xmlns='http://www.w3.org/1998/Math/MathML'), '')
-# math(xmlns='http://www.w3.org/1998/Math/MathML')
+# >>> print(parse_latex_math(math(), ''))
+# math()
# >>> parse_latex_math(math(), ' \\sqrt{ \\alpha}')
# math(msqrt(mi('α')))
# >>> parse_latex_math(math(), '23.4x')
@@ -703,13 +767,17 @@
# >>> parse_latex_math(math(), '\\sqrt[3]{2 + 3}')
# math(mroot(mrow(mn('2'), mo('+'), mn('3')), mn('3')))
# >>> parse_latex_math(math(), '\max_x') # function takes limits
-# math(munder(mi('max', movablelimits='true'), mi('x')))
+# math(munder(mo('max', movablelimits='true'), mi('x')))
# >>> parse_latex_math(math(), 'x^j_i') # ensure correct order: base, sub, sup
# math(msubsup(mi('x'), mi('i'), mi('j')))
# >>> parse_latex_math(math(), '\int^j_i') # ensure correct order
-# math(munderover(mo('∫', movablelimits='true'), mi('i'), mi('j')))
+# math(msubsup(mo('∫'), mi('i'), mi('j')))
+# >>> parse_latex_math(math(), 'x_{\\alpha}')
+# math(msub(mi('x'), mi('α')))
+# >>> parse_latex_math(math(), 'x_\\text{in}')
+# math(msub(mi('x'), mtext('in')))
-def handle_cmdname(name, node, string):
+def handle_cmd(name, node, string):
"""Process LaTeX command `name` followed by `string`.
Append result to `node`.
@@ -716,9 +784,9 @@
If needed, parse `string` for command argument.
Return new current node and remainder of `string`:
- >>> handle_cmdname('hbar', math(), r' \frac')
+ >>> handle_cmd('hbar', math(), r' \frac')
(math(mi('ℏ')), ' \\frac')
- >>> handle_cmdname('hspace', math(), r'{1ex} (x)')
+ >>> handle_cmd('hspace', math(), r'{1ex} (x)')
(math(mspace(width='1ex')), ' (x)')
"""
@@ -742,7 +810,8 @@
# use <mi> followed by invisible function applicator character
# (see https://www.w3.org/TR/MathML3/chapter3.html#presm.mi)
if name == 'operatorname':
- # custom function name ``\operatorname{abs}(x)``
+ # custom function name, e.g. ``\operatorname{abs}(x)``
+ # TODO: \operatorname* -> with limits
arg, string = tex_token(string)
new_node = mi(arg, mathvariant='normal')
else:
@@ -793,7 +862,7 @@
# operator, fence, or separator -> <mo>
if name == 'colon': # trailing punctuation, not binary relation
- node = node.append(mo(':', lspace='0', rspace='0.28em'))
+ node = node.append(mo(':', form='postfix', lspace='0', rspace='0.28em'))
return node, string
if name in thick_operators:
@@ -808,19 +877,25 @@
node = node.append(mo(operators[name]))
return node, string
- if name in ('left', 'right'):
- arg, string = tex_token(string)
- try:
- delimiter = stretchables[arg]
- except KeyError:
- raise SyntaxError(u'Missing %s delimiter!' % name)
- if name == 'left':
+ if name in delimiter_sizes:
+ delimiter_attributes = {}
+ size = delimiter_sizes[name]
+ delimiter, string = tex_token(string)
+ if delimiter not in '()[]/|.':
+ try:
+ delimiter = stretchables[delimiter.lstrip('\\')]
+ except KeyError:
+ raise SyntaxError(u'Missing "\\%s" delimiter!' % name)
+ if size:
+ delimiter_attributes['maxsize'] = size
+ delimiter_attributes['minsize'] = size
+ if name == 'left' or name.endswith('l'):
row = mrow()
node.append(row)
node = row
- if delimiter: # may be empty
- node.append(mo(delimiter))
- if name == 'right':
+ if delimiter != '.': # '.' stands for "empty delimiter"
+ node.append(mo(delimiter, **delimiter_attributes))
+ if name == 'right' or name.endswith('r'):
node = node.close()
return node, string
@@ -835,8 +910,13 @@
# arbitrary text (usually comments) -> <mtext>
if name in ('text', 'mbox', 'textrm'):
arg, string = tex_token(string)
- text = re.sub('(^ | $)', u'\u00a0', arg)
- node = node.append(mtext(text))
+ parts = arg.split('$') # extract inline math
+ for i, part in enumerate(parts):
+ if i % 2 == 0: # i is even
+ part = re.sub('(^ | $)', u'\u00a0', part)
+ node = node.append(mtext(part))
+ else:
+ parse_latex_math(node, part)
return node, string
# horizontal space -> <mspace>
@@ -849,6 +929,17 @@
node = node.append(mspace(width='%s'%arg))
return node, string
+ if name == 'phantom':
+ new_node = mphantom()
+ node.append(new_node)
+ return new_node, string
+
+ if name == 'boxed':
+ new_node = menclose(notation='box')
+ node.append(new_node)
+ return new_node, string
+
+
# Complex elements (Layout schemata)
# ==================================
@@ -864,10 +955,22 @@
node.append(new_node)
return new_node, string
- if name == 'frac':
- new_node = mfrac()
+ if name in fractions:
+ (style_atts, frac_atts) = fractions[name]
+ if name == 'cfrac':
+ optarg, string = tex_optarg(string)
+ optargs = {'l': 'left', 'r': 'right'}
+ if optarg in optargs:
+ frac_atts = frac_atts.copy()
+ frac_atts['numalign'] = optargs[optarg]
+ new_node = frac = mfrac(**frac_atts)
+ if name.endswith('binom'):
+ new_node = mrow(mo('('), new_node, mo(')'))
+ new_node.close()
+ if style_atts:
+ new_node = mstyle(new_node, **style_atts)
node.append(new_node)
- return new_node, string
+ return frac, string
if name == '\\': # end of a row
entry = mtd()
@@ -906,8 +1009,7 @@
base = mo(operators[name[1:]])
if subscript:
new_node = munderover(base)
- sub_node = mrow()
- parse_latex_math(sub_node, subscript)
+ sub_node = parse_latex_math(mrow(), subscript)
if len(sub_node) == 1:
sub_node = sub_node.children[0]
new_node.append(sub_node)
@@ -916,83 +1018,92 @@
node.append(new_node)
return new_node, string
- if name == 'begin':
- env_name, string = tex_token(string)
- if env_name == 'matrix':
- entry = mtd()
- table = mtable(mtr(entry))
- node.append(table)
- node = entry
- elif env_name == 'cases':
- entry = mtd()
- cases = mrow(mo('{'), mtable(mtr(entry)))
- node.append(cases)
- node = entry
+ if name in layout_styles: # 'displaystyle', 'textstyle', ...
+ new_node = mstyle(**layout_styles[name])
+ new_node.nchildren = None
+ if isinstance(node, mrow) and len(node) == 0:
+ # replace node with new_node
+ node.parent.children[node.parent.children.index(node)] = new_node
+ new_node.parent = node.parent
+ elif node.__class__.__name__ == 'math':
+ node.append(new_node)
else:
- raise SyntaxError(u'Environment not supported! '
- u'Supported environments: "matrix", "cases".')
+ raise SyntaxError(u'Declaration "\\%s" must be first command '
+ u'in a group.' % name)
+ return new_node, string
+
+ if name.endswith('limits'):
+ arg, remainder = tex_token(string)
+ if arg in '_^': # else ignore
+ string = remainder
+ node = handle_script_or_limit(node, arg, limits=name)
return node, string
+ # Environments
+
+ if name == 'begin':
+ return begin_environment(node, string)
+
if name == 'end':
- env_name, string = tex_token(string)
- if env_name == 'matrix':
- node = node.close().close().close()
- elif env_name == 'cases':
- node = node.close().close().close().close()
- else:
- raise SyntaxError(u'Environment not supported! '
- u'Supported environments: "matrix", "cases".')
- return node, string
+ return end_environment(node, string)
+
+
raise SyntaxError(u'Unknown LaTeX command: ' + name)
-
-# >>> handle_cmdname('left', math(), '[a\\right]')
+# >>> handle_cmd('left', math(), '[a\\right]')
# (mrow(mo('[')), 'a\\right]')
-# >>> handle_cmdname('left', math(), '. a)') # emtpy \left
+# >>> handle_cmd('left', math(), '. a)') # emtpy \left
# (mrow(), ' a)')
-# >>> handle_cmdname('left', math(), '\\uparrow a)') # cmd
+# >>> handle_cmd('left', math(), '\\uparrow a)') # cmd
# (mrow(mo('↑')), 'a)')
-# >>> handle_cmdname('not', math(), '\\equiv \\alpha)') # cmd
+# >>> handle_cmd('not', math(), '\\equiv \\alpha)') # cmd
# (math(mo('≢')), '\\alpha)')
-# >>> handle_cmdname('text', math(), '{ for } i>0') # group
+# >>> handle_cmd('text', math(), '{ for } i>0') # group
# (math(mtext('\xa0for\xa0')), ' i>0')
-# >>> handle_cmdname('text', math(), '{B}T') # group
+# >>> handle_cmd('text', math(), '{B}T') # group
# (math(mtext('B')), 'T')
-# >>> handle_cmdname('text', math(), '{number of apples}}') # group
+# >>> handle_cmd('text', math(), '{number of apples}}') # group
# (math(mtext('number of apples')), '}')
-# >>> handle_cmdname('text', math(), 'i \\sin(x)') # single char
+# >>> handle_cmd('text', math(), 'i \\sin(x)') # single char
# (math(mtext('i')), ' \\sin(x)')
-# >>> handle_cmdname('sin', math(), '(\\alpha)')
+# >>> handle_cmd('sin', math(), '(\\alpha)')
# (math(mi('sin'), mo('\u2061')), '(\\alpha)')
-# >>> handle_cmdname('sin', math(), ' \\alpha')
+# >>> handle_cmd('sin', math(), ' \\alpha')
# (math(mi('sin'), mo('\u2061')), ' \\alpha')
-# >>> handle_cmdname('operatorname', math(), '{abs}(x)')
+# >>> handle_cmd('operatorname', math(), '{abs}(x)')
# (math(mi('abs', mathvariant='normal'), mo('\u2061')), '(x)')
-# >>> handle_cmdname('mathrm', math(), '\\alpha')
+# >>> handle_cmd('mathrm', math(), '\\alpha')
# (math(mi('α', mathvariant='normal')), '')
-# >>> handle_cmdname('mathrm', math(), '{out} = 3')
+# >>> handle_cmd('mathrm', math(), '{out} = 3')
# (math(mi('out', mathvariant='normal')), ' = 3')
-# >>> handle_cmdname('overline', math(), '{981}')
+# >>> handle_cmd('overline', math(), '{981}')
# (mover(mo('¯'), switch=True, accent='false'), '{981}')
-# >>> handle_cmdname('bar', math(), '{x}')
+# >>> handle_cmd('bar', math(), '{x}')
# (mover(mo('ˉ'), switch=True, accent='true'), '{x}')
-# >>> handle_cmdname('xleftarrow', math(), r'[\alpha]{10}')
+# >>> handle_cmd('xleftarrow', math(), r'[\alpha]{10}')
# (munderover(mo('←'), mi('α')), '{10}')
-# >>> handle_cmdname('xleftarrow', math(), r'[\alpha=5]{10}')
+# >>> handle_cmd('xleftarrow', math(), r'[\alpha=5]{10}')
# (munderover(mo('←'), mrow(mi('α'), mo('='), mn('5'))), '{10}')
-def handle_script_or_limit(node, c):
+def handle_script_or_limit(node, c, limits=''):
"""Append script or limit element to `node`."""
child = node.children.pop()
+ if limits == 'limits':
+ child['movablelimits'] = 'false'
+ elif (limits == 'displaylimits'
+ or getattr(child, 'data', '') in displaylimits):
+ child['movablelimits'] = 'true'
+
if c == '_':
if isinstance(child, msup):
new_node = msubsup(*child.children, switch=True)
elif isinstance(child, mover):
new_node = munderover(*child.children, switch=True)
- elif getattr(child, 'data', '') in with_limits:
- child.attributes['movablelimits'] = 'true'
+ elif (limits in ('limits', 'displaylimits')
+ or limits == ''
+ and getattr(child, 'data', '') in displaylimits):
new_node = munder(child)
else:
new_node = msub(child)
@@ -1001,8 +1112,9 @@
new_node = msubsup(*child.children)
elif isinstance(child, munder):
new_node = munderover(*child.children)
- elif getattr(child, 'data', '') in with_limits:
- child.attributes['movablelimits'] = 'true'
+ elif (limits in ('limits', 'displaylimits')
+ or limits == ''
+ and getattr(child, 'data', '') in displaylimits):
new_node = mover(child)
else:
new_node = msup(child)
@@ -1010,6 +1122,44 @@
return new_node
+def begin_environment(node, string):
+ name, string = tex_token(string)
+ if name in matrices:
+ left_delimiter = matrices[name][0]
+ attributes = {}
+ if left_delimiter:
+ wrapper = mrow(mo(left_delimiter))
+ node.append(wrapper)
+ node = wrapper
+ elif name == 'smallmatrix':
+ attributes['rowspacing'] = '0.2em' # unimplemented in Firefox!
+ attributes['columnspacing'] = '0.333em'
+ wrapper = mstyle(scriptlevel=1)
+ node.append(wrapper)
+ node = wrapper
+ entry = mtd()
+ node.append(mtable(mtr(entry), **attributes))
+ node = entry
+ else:
+ raise SyntaxError(u'Environment not supported!')
+ return node, string
+
+
+def end_environment(node, string):
+ name, string = tex_token(string)
+ if name in matrices:
+ node = node.close().close().close() # close: mtd, mdr, mtable
+ right_delimiter = matrices[name][1]
+ if right_delimiter:
+ node = node.append(mo(right_delimiter))
+ node = node.close()
+ elif name == 'cases':
+ node = node.close()
+ else:
+ raise SyntaxError(u'Environment not supported!')
+ return node, string
+
+
def tex2mathml(tex_math, inline=True):
"""Return string with MathML code corresponding to `tex_math`.
@@ -1016,10 +1166,12 @@
Set `inline` to False for displayed math.
"""
# Set up tree
- tree = node = math(xmlns='http://www.w3.org/1998/Math/MathML')
- if not inline:
+ tree = math(xmlns='http://www.w3.org/1998/Math/MathML')
+ if inline:
+ node = tree
+ else:
# block: emulate align* environment with a math table
- tree.attributes['display']='block'
+ tree['display'] = 'block'
node = mtd()
tree.append(mtable(mtr(node), displaystyle='true', CLASS='align'))
@@ -1040,3 +1192,41 @@
# </mtr>
# </mtable>
# </math>
+
+# TODO: look up more symbols from tr25, e.g.
+#
+#
+# Table 2.8 Using Vertical Line or Solidus Overlay
+# some of the negated forms of mathematical relations that can only be
+# encoded by using either U+0338 COMBINING LONG SOLIDUS OVERLAY or U+20D2
+# COMBINING LONG VERTICAL LINE OVERLAY . (For issues with using 0338 in
+# MathML, see Section 3.2.7, Combining Marks.
+#
+# Table 2.9 Variants of Mathematical Symbols using VS1?
+#
+# Sequence Description
+# 0030 + VS1 DIGIT ZERO - short diagonal stroke form
+# 2205 + VS1 EMPTY SET - zero with long diagonal stroke overlay form
+# 2229 + VS1 INTERSECTION - with serifs
+# 222A + VS1 UNION - with serifs
+# 2268 + VS1 LESS-THAN BUT NOT EQUAL TO - with vertical stroke
+# 2269 + VS1 GREATER-THAN BUT NOT EQUAL TO - with vertical stroke
+# 2272 + VS1 LESS-THAN OR EQUIVALENT TO - following the slant of the lower leg
+# 2273 + VS1 GREATER-THAN OR EQUIVALENT TO - following the slant of the lower leg
+# 228A + VS1 SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members
+# 228B + VS1 SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members
+# 2293 + VS1 SQUARE CAP - with serifs
+# 2294 + VS1 SQUARE CUP - with serifs
+# 2295 + VS1 CIRCLED PLUS - with white rim
+# 2297 + VS1 CIRCLED TIMES - with white rim
+# 229C + VS1 CIRCLED EQUALS - equal sign inside and touching the circle
+# 22DA + VS1 LESS-THAN slanted EQUAL TO OR GREATER-THAN
+# 22DB + VS1 GREATER-THAN slanted EQUAL TO OR LESS-THAN
+# 2A3C + VS1 INTERIOR PRODUCT - tall variant with narrow foot
+# 2A3D + VS1 RIGHTHAND INTERIOR PRODUCT - tall variant with narrow foot
+# 2A9D + VS1 SIMILAR OR LESS-THAN - following the slant of the upper leg
+# 2A9E + VS1 SIMILAR OR GREATER-THAN - following the slant of the upper leg
+# 2AAC + VS1 SMALLER THAN OR slanted EQUAL
+# 2AAD + VS1 LARGER THAN OR slanted EQUAL
+# 2ACB + VS1 SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members
+# 2ACC + VS1 SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members
Modified: trunk/docutils/test/functional/expected/math_output_mathml.html
===================================================================
--- trunk/docutils/test/functional/expected/math_output_mathml.html 2021-05-20 12:25:30 UTC (rev 8757)
+++ trunk/docutils/test/functional/expected/math_output_mathml.html 2021-05-27 15:06:37 UTC (rev 8758)
@@ -152,11 +152,11 @@
<mtable class="align" displaystyle="true">
<mtr>
<mtd>
- <munderover>
- <mo movablelimits="true">∫</mo>
+ <msubsup>
+ <mo>∫</mo>
<mn>0</mn>
<mn>1</mn>
- </munderover>
+ </msubsup>
<msup>
<mi>x</mi>
<mi>n</mi>
@@ -383,14 +383,14 @@
<mo>(</mo>
<mfrac>
<mrow>
- <munderover>
- <mo movablelimits="true">∫</mo>
+ <msubsup>
+ <mo>∫</mo>
<mrow>
<mo>−</mo>
<mo>∞</mo>
</mrow>
<mo>∞</mo>
- </munderover>
+ </msubsup>
<mi>s</mi>
<mo stretchy="false">(</mo>
<mi>x</mi>
@@ -410,14 +410,14 @@
<mi>x</mi>
</mrow>
<mrow>
- <munderover>
- <mo movablelimits="true">∫</mo>
+ <msubsup>
+ <mo>∫</mo>
<mrow>
<mo>−</mo>
<mo>∞</mo>
</mrow>
<mo>∞</mo>
- </munderover>
+ </msubsup>
<mi>s</mi>
<mo stretchy="false">(</mo>
<mi>x</mi>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|