Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
95d09f2
Improve processing of MathMLForm for Input and Output Form
mmatera Jan 29, 2026
842fc58
remove trailing print
mmatera Jan 29, 2026
466bc6e
remove space from extra_operators, and add a comment
mmatera Jan 29, 2026
8f0f52b
add a comment about mathml multiline output. Give a more consistent o…
mmatera Jan 29, 2026
9e816d2
Merge branch 'master' into mathml_tweaks
mmatera Jan 30, 2026
81689de
Merge branch 'master' into mathml_tweaks
mmatera Jan 31, 2026
a5e3104
improve Expression.sameQ to deal with BoxExpressions. Add tests for B…
mmatera Feb 2, 2026
270f82d
add a test involving FractionBox
mmatera Feb 2, 2026
cb3ff7a
Merge branch 'improve_Expression_sameQ' into mathml_tweaks
mmatera Feb 2, 2026
70f84ed
Improve processing of MathMLForm for Input and Output Form
mmatera Jan 29, 2026
5b89c3f
remove trailing print
mmatera Jan 29, 2026
e88108d
remove space from extra_operators, and add a comment
mmatera Jan 29, 2026
5295f48
add a comment about mathml multiline output. Give a more consistent o…
mmatera Jan 29, 2026
1831e6b
improve Expression.sameQ to deal with BoxExpressions. Add tests for B…
mmatera Feb 2, 2026
6c529bd
Add Box type annotations to render routines.
rocky Feb 3, 2026
3c53f98
Fix type errors found by mypy
rocky Feb 3, 2026
942752a
More mypy/type work.
rocky Feb 3, 2026
6d25078
Merge branch 'master' into mathml_tweaks
rocky Feb 3, 2026
bddeb7a
merge
Feb 3, 2026
e57789a
merge
mmatera Feb 5, 2026
17413ed
merge
Feb 6, 2026
1e50e58
fix
Feb 6, 2026
262b628
merge
Feb 6, 2026
76bee53
merge
Feb 6, 2026
5488a13
fix mypy
Feb 6, 2026
684e474
ruff
mmatera Feb 6, 2026
7dbc388
Merge branch 'master' into mathml_tweaks
rocky Feb 7, 2026
351eee1
Update mathics/format/render/mathml.py
mmatera Feb 7, 2026
18abb93
Update mathics/format/render/mathml.py
mmatera Feb 7, 2026
8e17b74
fix syntactic typo
rocky Feb 7, 2026
42c6226
Apply suggestion from @rocky
rocky Feb 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion mathics/builtin/forms/print.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ class OutputForm(FormBaseClass):
in_outputforms = True
in_printforms = True

formats = {"OutputForm[s_String]": "s"}
summary_text = "format expression in plain text"


Expand Down
1 change: 0 additions & 1 deletion mathics/doc/doc_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import logging
import re
from abc import ABC
from os import getenv
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Sequence, Tuple

from mathics.core.evaluation import Message, Print, _Out
Expand Down
125 changes: 87 additions & 38 deletions mathics/format/render/mathml.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,32 @@
"""
Mathics3 Box rendering to MathML strings.

MathML rendering is usually initiated via MathMLForm[].
"""
MathML formatting is usually initiated in Mathics via MathMLForm[].

For readability, and following WMA MathML generated code, tags \
containing sub-tags are split on several lines, one by
sub element. For example, the Box expression

>> FractionBox[RowBox[{"a", "+", SuperscriptBox["b", "c"]}], "d"]

produces
```
<mfrac>
<mrow>
<mi>a</mi>
<mo>+</mo>
<msup>
<mi>b</mi>
<mi>c</mi>
</msup>
</mrow>
<mi>d</mi>
</mfrac>
```
In WMA, each line would be also indented adding one space on each \
level of indentation.

# Please see the developer note in __init__ about the use of "%s" in
# format strings.
"""

import base64

Expand Down Expand Up @@ -53,6 +74,7 @@ def encode_mathml(text: str) -> str:
return text


# "Operators" which are not in display_operators_set
extra_operators = {
",",
"(",
Expand All @@ -72,15 +94,18 @@ def encode_mathml(text: str) -> str:
named_characters["DifferentialD"],
}


add_conversion_fn(FormBox, convert_inner_box)


def fractionbox(box: FractionBox, **options) -> str:
# Note: values set in `options` take precedence over `box_options`
child_options = {**options, **box.box_options}

num_text = convert_box_to_format(box.num, **child_options)
den_text = convert_box_to_format(box.den, **child_options)
return "<mfrac>%s %s</mfrac>" % (num_text, den_text)

return "<mfrac>\n%s\n%s\n</mfrac>" % (num_text, den_text)


add_conversion_fn(FractionBox, fractionbox)
Expand All @@ -89,7 +114,7 @@ def fractionbox(box: FractionBox, **options) -> str:
def graphics3dbox(box: Graphics3DBox, elements=None, **options) -> str:
"""Turn the Graphics3DBox into a MathML string"""
result = box.boxes_to_js(**options)
result = f"<mtable><mtr><mtd>{result}</mtd></mtr></mtable>"
result = f"<mtable>\n<mtr>\n<mtd>\n{result}\n</mtd>\n</mtr>\n</mtable>"
return result


Expand Down Expand Up @@ -156,15 +181,39 @@ def gridbox(box: GridBox, elements=None, **box_options) -> str:
f"<mtd {joined_attrs} columnspan={num_fields}>%s</mtd>"
% convert_box_to_format(item, **box_options)
)

result += "</mtr>\n"
result += "</mtable>"
# print(f"gridbox: {result}")
return result


add_conversion_fn(GridBox, gridbox)
add_conversion_fn(InterpretationBox, convert_inner_box)


def interpretation_box(box: InterpretationBox, **options):
origin = box.expr
child_options = {**options, **box.box_options}
box = box.inner_box
if origin.has_form("InputForm", None):
# InputForm produce outputs of the form
# InterpretationBox[Style[_String, ...], origin_InputForm, opts___]
assert isinstance(box, StyleBox), f"boxes={box} is not a StyleBox."
box = box.inner_box
child_options["System`ShowStringCharacters"] = SymbolTrue
assert isinstance(box, String)
elif origin.has_form("OutputForm", None):
# OutputForm produce outputs of the form
# InterpretationBox[PaneBox[_String, ...], origin_OutputForm, opts___]
assert box.has_form("PaneBox", 1, None)
box = box.inner_box
assert isinstance(box, String)
# Remove the outer quotes
box = String(box.value)

return convert_box_to_format(box, **child_options)


add_conversion_fn(InterpretationBox, interpretation_box)


def pane_box(box: PaneBox, **options):
Expand Down Expand Up @@ -239,20 +288,24 @@ def is_list_interior(content):

# print(f"mrow: {result}")

return "<mrow>%s</mrow>" % " ".join(result)
return "<mrow>\n%s\n</mrow>" % "\n".join(result)


add_conversion_fn(RowBox, rowbox)


def sqrtbox(box: SqrtBox, **options):
# Note: values set in `options` take precedence over `box_options`
child_options = {**options, **box.box_options}
if box.index:
return "<mroot> %s %s </mroot>" % (
convert_inner_box_field(box, "radicand", **options),
convert_inner_box_field(box, "index", **options),
convert_inner_box_field(box, "radicand", **child_options),
convert_inner_box_field(box, "index", **child_options),
)

return "<msqrt> %s </msqrt>" % convert_inner_box_field(box, "radicand", **options)
return "<msqrt>\n%s\n</msqrt>" % convert_inner_box_field(
box, "radicand", **child_options
)


add_conversion_fn(SqrtBox, sqrtbox)
Expand All @@ -274,13 +327,10 @@ def render(format, string):
return format % encoded_text

if text.startswith('"') and text.endswith('"'):
if show_string_characters:
return render("<ms>%s</ms>", text[1:-1])
else:
outtext = ""
for line in text[1:-1].split("\n"):
outtext += render("<mtext>%s</mtext>", line)
return outtext
text = text[1:-1]
if not show_string_characters:
return render("<mtext>%s</mtext>", text)
return render("<ms>%s</ms>", text)
elif (
text
and (number_as_text is SymbolFalse)
Expand All @@ -295,31 +345,28 @@ def render(format, string):
# Mathics-Django:
if text == "":
return ""
if text == "\u2146":
return render(
'<mo form="prefix" lspace="0.2em" rspace="0">%s</mo>', text
)
if text == "\u2062":
if text == named_characters["InvisibleTimes"]:
return render(
'<mo form="prefix" lspace="0" rspace="0.2em">%s</mo>', text
)
return render("<mo>%s</mo>", text)
elif is_symbol_name(text):
return render("<mi>%s</mi>", text)
else:
outtext = ""
for line in text.split("\n"):
outtext += render("<mtext>%s</mtext>", line)
return outtext
return "".join(
render("<mtext>%s</mtext>", line) for line in text.split("\n")
)


add_conversion_fn(String, string)


def subscriptbox(box: SubscriptBox, **options):
return "<msub>%s %s</msub>" % (
convert_inner_box_field(box, "base", **options),
convert_inner_box_field(box, "subindex", **options),
# Note: values set in `options` take precedence over `box_options`
child_options = {**options, **box.box_options}
return "<msub>\n%s\n%s\n</msub>" % (
convert_inner_box_field(box, "base", **child_options),
convert_inner_box_field(box, "subindex", **child_options),
)


Expand All @@ -328,11 +375,12 @@ def subscriptbox(box: SubscriptBox, **options):

def subsuperscriptbox(box: SubsuperscriptBox, **options):
# Note: values set in `options` take precedence over `box_options`
child_options = {**box.box_options, **options}
box.base.inside_row = box.subindex.inside_row = box.superindex.inside_row = True
return "<msubsup>%s %s %s</msubsup>" % (
convert_inner_box_field(box, "base", **options),
convert_inner_box_field(box, "subindex", **options),
convert_inner_box_field(box, "superindex", **options),
return "<msubsup>\n%s\n%s\n%s\n</msubsup>" % (
convert_inner_box_field(box, "base", **child_options),
convert_inner_box_field(box, "subindex", **child_options),
convert_inner_box_field(box, "superindex", **child_options),
)


Expand All @@ -341,9 +389,10 @@ def subsuperscriptbox(box: SubsuperscriptBox, **options):

def superscriptbox(box: SuperscriptBox, **options):
# Note: values set in `options` take precedence over `box_options`
return "<msup>%s %s</msup>" % (
convert_inner_box_field(box, "base", **options),
convert_inner_box_field(box, "superindex", **options),
child_options = {**options, **box.box_options}
return "<msup>\n%s\n%s\n</msup>" % (
convert_inner_box_field(box, "base", **child_options),
convert_inner_box_field(box, "superindex", **child_options),
)


Expand Down
2 changes: 1 addition & 1 deletion test/builtin/box/test_custom_boxexpression.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from mathics.core.builtin import Predefined
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.rules import BaseRule, FunctionApplyRule, Rule
from mathics.core.rules import FunctionApplyRule
from mathics.core.symbols import Symbol

SymbolCustomGraphicsBox = Symbol("CustomGraphicsBox")
Expand Down
1 change: 0 additions & 1 deletion test/builtin/test_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"""


import os
from test.helper import check_evaluation

import pytest
Expand Down
Loading
Loading