SF.net SVN: docutils:[9528 ] trunk/docutils/docutils/u tils/math/latex2mathml.py
milde--- via Docutils-checkins <[email protected]>
| Newsgroups | gmane.text.docutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 9528
http://sourceforge.net/p/docutils/code/9528
Author: milde
Date: 2024-02-01 13:02:42 +0000 (Thu, 01 Feb 2024)
Log Message:
-----------
latex2mathml: better error reports.
Keep a copy of the math input for error reporting.
Don't merge runs of whitespace in the math input to a single space:
enables reporting error spot.
Raise MathError error when converting TeX to MathML leaves nodes with less
than required child-elements.
(This is usually a sign of a TeX command missing an argument.)
Modified Paths:
--------------
trunk/docutils/docutils/utils/math/latex2mathml.py
Modified: trunk/docutils/docutils/utils/math/latex2mathml.py
===================================================================
--- trunk/docutils/docutils/utils/math/latex2mathml.py 2024-02-01 13:02:33 UTC (rev 9527)
+++ trunk/docutils/docutils/utils/math/latex2mathml.py 2024-02-01 13:02:42 UTC (rev 9528)
@@ -214,6 +214,7 @@
'thickspace': '0.2778em', # 5mu = 5/18em
';': '0.2778em', # 5mu thickspace
' ': '0.25em', # inter word space
+ '\n': '0.25em', # inter word space
'medspace': '0.2222em', # 4mu = 2/9em
':': '0.2222em', # 4mu medspace
'thinspace': '0.1667em', # 3mu = 1/6em
@@ -342,20 +343,26 @@
('_', '2')
"""
- m = re.match(r'([a-zA-Z]+) *(.*)', string)
+ m = re.match(r'([a-zA-Z]+)[ \n]*(.*)', string, re.DOTALL)
if m is None:
- m = re.match(r'(.?)(.*)', string)
+ m = re.match(r'(.?)(.*)', string, re.DOTALL)
return m.group(1), m.group(2)
# Test:
#
+# >>> tex_cmdname('name\nnext') # strip trailing whitespace, also newlines
+# ('name', 'next')
# >>> tex_cmdname('name_2') # first non-letter terminates
# ('name', '_2')
+# >>> tex_cmdname('name_2\nnext line') # line-break allowed
+# ('name', '_2\nnext line')
# >>> tex_cmdname(' next') # leading whitespace is returned
# (' ', 'next')
# >>> tex_cmdname('1 2') # whitespace after non-letter is kept
# ('1', ' 2')
+# >>> tex_cmdname('1\n2\t3') # whitespace after non-letter is kept
+# ('1', '\n2\t3')
# >>> tex_cmdname('') # empty string
# ('', '')
@@ -367,7 +374,7 @@
('123.4', '')
"""
- m = re.match(r'([0-9.,]*[0-9]+)(.*)', string)
+ m = re.match(r'([0-9.,]*[0-9]+)(.*)', string, re.DOTALL)
if m is None:
return '', string
return m.group(1), m.group(2)
@@ -400,7 +407,7 @@
|(?P<chcmd>\\.) # one-character TeX command
|(?P<ch>.?)) # first character (or empty)
(?P<remainder>.*$) # remaining part of string
- """, string, re.VERBOSE)
+ """, string, re.VERBOSE | re.DOTALL)
cmd, chcmd, ch, remainder = m.group('cmd', 'chcmd', 'ch', 'remainder')
return cmd or chcmd or ch, remainder
@@ -501,7 +508,7 @@
m = re.match(r"""\s* # leading whitespace
\[(?P<optarg>(\\]|[^\[\]]|\\])*)\] # [group] without nested groups
(?P<remainder>.*$)
- """, string, re.VERBOSE)
+ """, string, re.VERBOSE | re.DOTALL)
if m is None and not string.startswith('['):
return '', string
try:
@@ -522,7 +529,7 @@
# docutils.utils.math.MathError: Could not extract optional argument from "[group with [nested group]]"!
-def parse_latex_math(node, string):
+def parse_latex_math(root, source):
"""Append MathML conversion of `string` to `node` and return it.
>>> parse_latex_math(math(), r'\alpha')
@@ -532,14 +539,15 @@
"""
# Normalize white-space:
- string = ' '.join(string.split())
- tree = node
+ string = source # not-yet handled part of source
+ node = root # the current "insertion point"
+ # Loop over `string` while changing it.
while len(string) > 0:
# Take off first character:
c, string = string[0], string[1:]
- if c == ' ':
+ if c in ' \n':
continue # whitespace is ignored in LaTeX math mode
if c == '\\': # start of a LaTeX macro
cmdname, string = tex_cmdname(string)
@@ -582,11 +590,18 @@
else:
raise MathError(f'Unsupported character: "{c}"!')
# TODO: append as <mi>?
- return tree
+ if node is None:
+ if not string:
+ return root # ignore unbalanced braces
+ raise MathError(f'No insertion point for "{string}". '
+ f'Unbalanced braces in "{source[:-len(string)]}"?')
+ if node.nchildren and len(node) < node.nchildren:
+ raise MathError('Last node missing children. Source incomplete?')
+ return root
# Test:
-# >>> print(parse_latex_math(math(), ''))
+# >>> parse_latex_math(math(), '')
# math()
# >>> parse_latex_math(math(), ' \\sqrt{ \\alpha}')
# math(msqrt(mi('α')))
@@ -594,8 +609,8 @@
# math(mn('23.4'), mi('x'))
# >>> parse_latex_math(math(), '\\sqrt 2 \\ne 3')
# math(msqrt(mn('2')), mo('≠'), mn('3'))
-# >>> parse_latex_math(math(), '\\sqrt{2 + 3} < 3')
-# math(msqrt(mn('2'), mo('+'), mn('3')), mo('<'), mn('3'))
+# >>> parse_latex_math(math(), '\\sqrt{2 + 3} < 10')
+# math(msqrt(mn('2'), mo('+'), mn('3')), mo('<'), mn('10'))
# >>> 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
@@ -611,6 +626,14 @@
# >>> parse_latex_math(math(), '2⌘')
# Traceback (most recent call last):
# docutils.utils.math.MathError: Unsupported character: "⌘"!
+# >>> parse_latex_math(math(), '23}x') # doctest: +ELLIPSIS
+# Traceback (most recent call last):
+# ...
+# docutils.utils.math.MathError: ... Unbalanced braces in "23}"?
+# >>> parse_latex_math(math(), '\\frac{2}')
+# Traceback (most recent call last):
+# ...
+# docutils.utils.math.MathError: Last node missing children. Source incomplete?
def handle_cmd(name, node, string): # noqa: C901 TODO make this less complex
@@ -777,7 +800,10 @@
parts = arg.split('$') # extract inline math
for i, part in enumerate(parts):
if i % 2 == 0: # i is even
- part = re.sub('(^ | $)', '\u00a0', part)
+ # LaTeX keeps whitespace in, e.g., ``\text{ foo }``,
+ # <mtext> displays only internal whitespace.
+ # → replace marginal whitespace with NBSP
+ part = re.sub('(^[ \n]|[ \n]$)', '\u00a0', part)
node = node.append(mtext(part))
else:
parse_latex_math(node, part)
@@ -939,6 +965,10 @@
# (math(mtext('number of apples')), '}')
# >>> handle_cmd('text', math(), 'i \\sin(x)') # single char
# (math(mtext('i')), ' \\sin(x)')
+# >>> handle_cmd(' ', math(), ' next') # inter word space
+# (math(mspace(width='0.25em')), ' next')
+# >>> handle_cmd('\n', math(), '\nnext') # inter word space
+# (math(mspace(width='0.25em')), '\nnext')
# >>> handle_cmd('sin', math(), '(\\alpha)')
# (math(mi('sin'), mo('\u2061')), '(\\alpha)')
# >>> handle_cmd('sin', math(), ' \\alpha')
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