feat: referentiel projets dynamique + validation nommage by design
Durcit la convention de nommage des projets (dérive constatée : 'Sliding Automation', 'code_versioning'... au lieu des formes canoniques). - trilium_api.py : projets_canoniques() lit le référentiel = valeurs du label projet sur les notes de type=projet (source unique, pas de constante en dur). Note-projet CodeVersioning créée (manquait). - mcp_server.py : _valider_projet() branché dans les 6 tools de création (add_decision/history/backlog, new_conversation, create_entite, add_skill). Refuse un projet non canonique (suggestion si faute) ou inconnu (renvoi au processus de création de projet). Ne verrouille pas si référentiel illisible. - lint_audit.py : VAL-nommage aligné sur le référentiel (attrape casse, espace ET snake_case ; l'ancien 'contient un espace' ratait code_versioning). - Données : 79 notes ré-étiquetées vers les 3 formes canoniques. Quality by design : l'erreur de nommage devient impossible à l'écriture, le Lint n'est plus que le filet de sécurité.
This commit is contained in:
@@ -0,0 +1,595 @@
|
||||
import copy
|
||||
import enum
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from typing import Iterable, Iterator, Optional
|
||||
from xml.etree.cElementTree import Element, SubElement, tostring
|
||||
from xml.sax.saxutils import unescape
|
||||
|
||||
from latex2mathml import commands
|
||||
from latex2mathml.symbols_parser import convert_symbol
|
||||
from latex2mathml.walker import Node, walk
|
||||
|
||||
COLUMN_ALIGNMENT_MAP = {"r": "right", "l": "left", "c": "center"}
|
||||
OPERATORS = (
|
||||
"+",
|
||||
"-",
|
||||
"*",
|
||||
"/",
|
||||
"(",
|
||||
")",
|
||||
"=",
|
||||
",",
|
||||
"?",
|
||||
"[",
|
||||
"]",
|
||||
"|",
|
||||
r"\|",
|
||||
"!",
|
||||
r"\{",
|
||||
r"\}",
|
||||
r">",
|
||||
r"<",
|
||||
r".",
|
||||
r"\bigotimes",
|
||||
r"\centerdot",
|
||||
r"\dots",
|
||||
r"\dotsc",
|
||||
r"\dotso",
|
||||
r"\gt",
|
||||
r"\ldotp",
|
||||
r"\lt",
|
||||
r"\lvert",
|
||||
r"\lVert",
|
||||
r"\lvertneqq",
|
||||
r"\ngeqq",
|
||||
r"\omicron",
|
||||
r"\rvert",
|
||||
r"\rVert",
|
||||
r"\S",
|
||||
r"\smallfrown",
|
||||
r"\smallint",
|
||||
r"\smallsmile",
|
||||
r"\surd",
|
||||
r"\varsubsetneqq",
|
||||
r"\varsupsetneqq",
|
||||
)
|
||||
MATH_MODE_PATTERN = re.compile(r"\\\$|\$|\\?[^\\$]+")
|
||||
|
||||
|
||||
class Mode(enum.Enum):
|
||||
TEXT = enum.auto()
|
||||
MATH = enum.auto()
|
||||
|
||||
|
||||
def convert(
|
||||
latex: str,
|
||||
xmlns: str = "http://www.w3.org/1998/Math/MathML",
|
||||
display: str = "inline",
|
||||
parent: Optional[Element] = None,
|
||||
) -> str:
|
||||
math = convert_to_element(latex, xmlns, display, parent)
|
||||
return _convert(math)
|
||||
|
||||
|
||||
def convert_to_element(
|
||||
latex: str,
|
||||
xmlns: str = "http://www.w3.org/1998/Math/MathML",
|
||||
display: str = "inline",
|
||||
parent: Optional[Element] = None,
|
||||
) -> Element:
|
||||
tag = "math"
|
||||
attrib = {"xmlns": xmlns, "display": display}
|
||||
math = Element(tag, attrib) if parent is None else SubElement(parent, tag, attrib)
|
||||
row = SubElement(math, "mrow")
|
||||
_convert_group(iter(walk(latex, display)), row)
|
||||
return math
|
||||
|
||||
|
||||
def _convert(tree: Element) -> str:
|
||||
return unescape(tostring(tree, encoding="unicode"))
|
||||
|
||||
|
||||
def _convert_matrix(nodes: Iterator[Node], parent: Element, command: str, alignment: Optional[str] = None) -> None:
|
||||
row = None
|
||||
cell = None
|
||||
|
||||
col_index = 0
|
||||
col_alignment = None
|
||||
|
||||
max_col_size = 0
|
||||
|
||||
row_index = 0
|
||||
row_lines = []
|
||||
|
||||
hfil_indexes: list[bool] = []
|
||||
|
||||
for node in nodes:
|
||||
if row is None:
|
||||
row = SubElement(parent, "mtr")
|
||||
|
||||
if cell is None:
|
||||
col_alignment, col_index = _get_column_alignment(alignment, col_alignment, col_index)
|
||||
cell = _make_matrix_cell(row, col_alignment)
|
||||
|
||||
if node.token == commands.BRACES:
|
||||
_convert_group(iter([node]), cell)
|
||||
elif node.token == "&":
|
||||
_set_cell_alignment(cell, hfil_indexes)
|
||||
hfil_indexes = []
|
||||
col_alignment, col_index = _get_column_alignment(alignment, col_alignment, col_index)
|
||||
cell = _make_matrix_cell(row, col_alignment)
|
||||
if command in (commands.SPLIT, commands.ALIGN) and col_index % 2 == 0:
|
||||
SubElement(cell, "mi")
|
||||
elif node.token in (commands.DOUBLEBACKSLASH, commands.CARRIAGE_RETURN):
|
||||
_set_cell_alignment(cell, hfil_indexes)
|
||||
hfil_indexes = []
|
||||
row_index += 1
|
||||
if col_index > max_col_size:
|
||||
max_col_size = col_index
|
||||
col_index = 0
|
||||
col_alignment, col_index = _get_column_alignment(alignment, col_alignment, col_index)
|
||||
row = SubElement(parent, "mtr")
|
||||
cell = _make_matrix_cell(row, col_alignment)
|
||||
elif node.token == commands.HLINE:
|
||||
row_lines.append("solid")
|
||||
elif node.token == commands.HDASHLINE:
|
||||
row_lines.append("dashed")
|
||||
elif node.token == commands.HFIL:
|
||||
hfil_indexes.append(True)
|
||||
else:
|
||||
if row_index > len(row_lines):
|
||||
row_lines.append("none")
|
||||
hfil_indexes.append(False)
|
||||
_convert_group(iter([node]), cell)
|
||||
|
||||
if col_index > max_col_size:
|
||||
max_col_size = col_index
|
||||
|
||||
if any(r == "solid" for r in row_lines):
|
||||
parent.set("rowlines", " ".join(row_lines))
|
||||
|
||||
if row is not None and cell is not None and len(cell) == 0:
|
||||
# Remove last row if it does not contain anything
|
||||
parent.remove(row)
|
||||
|
||||
if max_col_size and command == commands.ALIGN:
|
||||
spacing = ("0em", "2em")
|
||||
multiplier = max_col_size // len(spacing)
|
||||
parent.set("columnspacing", " ".join(spacing * multiplier))
|
||||
|
||||
|
||||
def _set_cell_alignment(cell: Element, hfil_indexes: list[bool]) -> None:
|
||||
if cell is not None and any(hfil_indexes) and len(hfil_indexes) > 1:
|
||||
if hfil_indexes[0] and not hfil_indexes[-1]:
|
||||
cell.attrib["columnalign"] = "right"
|
||||
elif not hfil_indexes[0] and hfil_indexes[-1]:
|
||||
cell.attrib["columnalign"] = "left"
|
||||
|
||||
|
||||
def _get_column_alignment(
|
||||
alignment: Optional[str], column_alignment: Optional[str], column_index: int
|
||||
) -> tuple[Optional[str], int]:
|
||||
if alignment:
|
||||
try:
|
||||
column_alignment = COLUMN_ALIGNMENT_MAP.get(alignment[column_index])
|
||||
except IndexError:
|
||||
column_alignment = COLUMN_ALIGNMENT_MAP.get(alignment[column_index % len(alignment)])
|
||||
column_index += 1
|
||||
return column_alignment, column_index
|
||||
|
||||
|
||||
def _make_matrix_cell(row: Element, column_alignment: Optional[str]) -> Element:
|
||||
if column_alignment:
|
||||
return SubElement(row, "mtd", columnalign=column_alignment)
|
||||
return SubElement(row, "mtd")
|
||||
|
||||
|
||||
def _convert_group(nodes: Iterable[Node], parent: Element, font: Optional[dict[str, Optional[str]]] = None) -> None:
|
||||
_font = font
|
||||
for node in nodes:
|
||||
token = node.token
|
||||
if token in (*commands.MSTYLE_SIZES, *commands.STYLES):
|
||||
node = Node(token=token, children=tuple(n for n in nodes))
|
||||
_convert_command(node, parent, _font)
|
||||
elif token in commands.CONVERSION_MAP or token in (commands.MOD, commands.PMOD):
|
||||
_convert_command(node, parent, _font)
|
||||
elif token in commands.LOCAL_FONTS and node.children is not None:
|
||||
_convert_group(iter(node.children), parent, commands.LOCAL_FONTS[token])
|
||||
elif token.startswith(commands.MATH) and node.children is not None:
|
||||
_convert_group(iter(node.children), parent, _font)
|
||||
elif token in commands.GLOBAL_FONTS.keys():
|
||||
_font = commands.GLOBAL_FONTS.get(token)
|
||||
elif node.children is None:
|
||||
_convert_symbol(node, parent, _font)
|
||||
elif node.children is not None:
|
||||
attributes = node.attributes or {}
|
||||
_row = SubElement(parent, "mrow", attrib=attributes)
|
||||
_convert_group(iter(node.children), _row, _font)
|
||||
|
||||
|
||||
def _get_alignment_and_column_lines(alignment: Optional[str] = None) -> tuple[Optional[str], Optional[str]]:
|
||||
if alignment is None:
|
||||
return None, None
|
||||
if "|" not in alignment:
|
||||
return alignment, None
|
||||
_alignment = ""
|
||||
column_lines = []
|
||||
for c in alignment:
|
||||
if c == "|":
|
||||
column_lines.append("solid")
|
||||
else:
|
||||
_alignment += c
|
||||
if len(_alignment) - len(column_lines) == 2:
|
||||
column_lines.append("none")
|
||||
return _alignment, " ".join(column_lines)
|
||||
|
||||
|
||||
def separate_by_mode(text: str) -> Iterator[tuple[str, Mode]]:
|
||||
string = ""
|
||||
is_math_mode = False
|
||||
for match in MATH_MODE_PATTERN.findall(text):
|
||||
if match == "$": # should match both $ and $$
|
||||
yield string, Mode.MATH if is_math_mode else Mode.TEXT
|
||||
string = ""
|
||||
is_math_mode = not is_math_mode
|
||||
else:
|
||||
string += match
|
||||
if len(string):
|
||||
yield string, Mode.MATH if is_math_mode else Mode.TEXT
|
||||
# TODO: if stays in math mode, means not terminated properly, raise error
|
||||
|
||||
|
||||
def _convert_command(node: Node, parent: Element, font: Optional[dict[str, Optional[str]]] = None) -> None:
|
||||
command = node.token
|
||||
modifier = node.modifier
|
||||
|
||||
if command in (commands.SUBSTACK, commands.SMALLMATRIX):
|
||||
parent = SubElement(parent, "mstyle", scriptlevel="1")
|
||||
elif command == commands.CASES:
|
||||
parent = SubElement(parent, "mrow")
|
||||
lbrace = SubElement(parent, "mo", OrderedDict([("stretchy", "true"), ("fence", "true"), ("form", "prefix")]))
|
||||
lbrace.text = "&#x{};".format(convert_symbol(commands.LBRACE))
|
||||
elif command in (commands.DBINOM, commands.DFRAC):
|
||||
parent = SubElement(parent, "mstyle", displaystyle="true", scriptlevel="0")
|
||||
elif command == commands.HPHANTOM:
|
||||
parent = SubElement(parent, "mpadded", height="0", depth="0")
|
||||
elif command == commands.VPHANTOM:
|
||||
parent = SubElement(parent, "mpadded", width="0")
|
||||
elif command in (commands.TBINOM, commands.HBOX, commands.MBOX, commands.TFRAC):
|
||||
parent = SubElement(parent, "mstyle", displaystyle="false", scriptlevel="0")
|
||||
elif command in (commands.MOD, commands.PMOD):
|
||||
SubElement(parent, "mspace", width="1em")
|
||||
|
||||
tag, attributes = copy.deepcopy(commands.CONVERSION_MAP[command])
|
||||
|
||||
if node.attributes is not None and node.token != commands.SKEW:
|
||||
attributes.update(node.attributes)
|
||||
|
||||
if command == commands.LEFT:
|
||||
parent = SubElement(parent, "mrow")
|
||||
|
||||
_append_prefix_element(node, parent)
|
||||
|
||||
alignment, column_lines = _get_alignment_and_column_lines(node.alignment)
|
||||
|
||||
if column_lines:
|
||||
attributes["columnlines"] = column_lines
|
||||
|
||||
if command == commands.SUBSUP and node.children is not None and node.children[0].token == commands.GCD:
|
||||
tag = "munderover"
|
||||
elif command == commands.SUPERSCRIPT and modifier in (commands.LIMITS, commands.OVERBRACE):
|
||||
tag = "mover"
|
||||
elif command == commands.SUBSCRIPT and modifier in (commands.LIMITS, commands.UNDERBRACE):
|
||||
tag = "munder"
|
||||
elif command == commands.SUBSUP and modifier in (commands.LIMITS, commands.OVERBRACE, commands.UNDERBRACE):
|
||||
tag = "munderover"
|
||||
elif (
|
||||
command in (commands.XLEFTARROW, commands.XRIGHTARROW) and node.children is not None and len(node.children) == 2
|
||||
):
|
||||
tag = "munderover"
|
||||
|
||||
element = SubElement(parent, tag, attributes)
|
||||
|
||||
if command in commands.LIMIT:
|
||||
element.text = command[1:]
|
||||
elif command in (commands.MOD, commands.PMOD):
|
||||
element.text = "mod"
|
||||
SubElement(parent, "mspace", width="0.333em")
|
||||
elif command == commands.BMOD:
|
||||
element.text = "mod"
|
||||
elif command in (commands.XLEFTARROW, commands.XRIGHTARROW):
|
||||
style = SubElement(element, "mstyle", scriptlevel="0")
|
||||
arrow = SubElement(style, "mo")
|
||||
if command == commands.XLEFTARROW:
|
||||
arrow.text = "←"
|
||||
elif command == commands.XRIGHTARROW:
|
||||
arrow.text = "→"
|
||||
elif node.text is not None:
|
||||
if command == commands.MIDDLE:
|
||||
element.text = "&#x{};".format(convert_symbol(node.text))
|
||||
elif command == commands.HBOX:
|
||||
mtext: Optional[Element] = element
|
||||
for text, mode in separate_by_mode(node.text):
|
||||
if mode == Mode.TEXT:
|
||||
if mtext is None:
|
||||
mtext = SubElement(parent, tag, attributes)
|
||||
mtext.text = text.replace(" ", " ")
|
||||
_set_font(mtext, "mtext", font)
|
||||
mtext = None
|
||||
else:
|
||||
_row = SubElement(parent, "mrow")
|
||||
_convert_group(iter(walk(text)), _row)
|
||||
else:
|
||||
if command == commands.FBOX:
|
||||
element = SubElement(element, "mtext")
|
||||
element.text = node.text.replace(" ", " ")
|
||||
_set_font(element, "mtext", font)
|
||||
elif node.delimiter is not None and command not in (commands.FRAC, commands.GENFRAC):
|
||||
if node.delimiter != ".":
|
||||
symbol = convert_symbol(node.delimiter)
|
||||
element.text = node.delimiter if symbol is None else "&#x{};".format(symbol)
|
||||
|
||||
if node.children is not None:
|
||||
_parent = element
|
||||
if command in (commands.LEFT, commands.MOD, commands.PMOD):
|
||||
_parent = parent
|
||||
if command in commands.MATRICES:
|
||||
if command == commands.CASES:
|
||||
alignment = "l"
|
||||
elif command in (commands.SPLIT, commands.ALIGN):
|
||||
alignment = "rl"
|
||||
_convert_matrix(iter(node.children), _parent, command, alignment=alignment)
|
||||
elif command == commands.CFRAC:
|
||||
for child in node.children:
|
||||
p = SubElement(_parent, "mstyle", displaystyle="false", scriptlevel="0")
|
||||
_convert_group(iter([child]), p, font)
|
||||
elif command == commands.SIDESET:
|
||||
Node(
|
||||
r"\style",
|
||||
children=(Node(r"\mspace", attributes={"width": "-0.167em"}),),
|
||||
attributes={"scriptlevel": "0"},
|
||||
),
|
||||
left, right = node.children
|
||||
_convert_group(iter([left]), _parent, font)
|
||||
fill = SubElement(_parent, "mstyle", scriptlevel="0")
|
||||
SubElement(fill, "mspace", width="-0.167em")
|
||||
_convert_group(iter([right]), _parent, font)
|
||||
elif command == commands.SKEW:
|
||||
child = node.children[0]
|
||||
new_node = Node(
|
||||
token=child.token,
|
||||
children=(
|
||||
Node(
|
||||
token=commands.BRACES,
|
||||
children=(*child.children, Node(token=commands.MKERN, attributes=node.attributes)),
|
||||
),
|
||||
),
|
||||
)
|
||||
_convert_group(iter([new_node]), _parent, font)
|
||||
elif command in (commands.XLEFTARROW, commands.XRIGHTARROW):
|
||||
for child in node.children:
|
||||
padded = SubElement(
|
||||
_parent,
|
||||
"mpadded",
|
||||
OrderedDict(
|
||||
[("width", "+0.833em"), ("lspace", "0.556em"), ("voffset", "-.2em"), ("height", "-.2em")]
|
||||
),
|
||||
)
|
||||
_convert_group(iter([child]), padded, font)
|
||||
SubElement(padded, "mspace", depth=".25em")
|
||||
else:
|
||||
_convert_group(iter(node.children), _parent, font)
|
||||
|
||||
_add_diacritic(command, element)
|
||||
|
||||
_append_postfix_element(node, parent)
|
||||
|
||||
|
||||
def _add_diacritic(command: str, parent: Element) -> None:
|
||||
if command in commands.DIACRITICS:
|
||||
text, attributes = copy.deepcopy(commands.DIACRITICS[command])
|
||||
element = SubElement(parent, "mo", attributes)
|
||||
element.text = text
|
||||
|
||||
|
||||
def _convert_and_append_command(command: str, parent: Element, attributes: Optional[dict[str, str]] = None) -> None:
|
||||
code_point = convert_symbol(command)
|
||||
mo = SubElement(parent, "mo", attributes if attributes is not None else {})
|
||||
mo.text = "&#x{};".format(code_point) if code_point else command
|
||||
|
||||
|
||||
def _append_prefix_element(node: Node, parent: Element) -> None:
|
||||
size = "2.047em"
|
||||
if parent.attrib.get("displaystyle") == "false" or node.token == commands.TBINOM:
|
||||
size = "1.2em"
|
||||
if node.token in (r"\pmatrix", commands.PMOD):
|
||||
_convert_and_append_command(r"\lparen", parent)
|
||||
elif node.token in (commands.BINOM, commands.DBINOM, commands.TBINOM):
|
||||
_convert_and_append_command(r"\lparen", parent, {"minsize": size, "maxsize": size})
|
||||
elif node.token == r"\bmatrix":
|
||||
_convert_and_append_command(r"\lbrack", parent)
|
||||
elif node.token == r"\Bmatrix":
|
||||
_convert_and_append_command(r"\lbrace", parent)
|
||||
elif node.token == r"\vmatrix":
|
||||
_convert_and_append_command(r"\vert", parent)
|
||||
elif node.token == r"\Vmatrix":
|
||||
_convert_and_append_command(r"\Vert", parent)
|
||||
elif node.token in (commands.FRAC, commands.GENFRAC) and node.delimiter is not None and node.delimiter[0] != ".":
|
||||
# TODO: use 1.2em if inline
|
||||
_convert_and_append_command(node.delimiter[0], parent, {"minsize": size, "maxsize": size})
|
||||
|
||||
|
||||
def _append_postfix_element(node: Node, parent: Element) -> None:
|
||||
size = "2.047em"
|
||||
if parent.attrib.get("displaystyle") == "false" or node.token == commands.TBINOM:
|
||||
size = "1.2em"
|
||||
if node.token in (r"\pmatrix", commands.PMOD):
|
||||
_convert_and_append_command(r"\rparen", parent)
|
||||
elif node.token in (commands.BINOM, commands.DBINOM, commands.TBINOM):
|
||||
_convert_and_append_command(r"\rparen", parent, {"minsize": size, "maxsize": size})
|
||||
elif node.token == r"\bmatrix":
|
||||
_convert_and_append_command(r"\rbrack", parent)
|
||||
elif node.token == r"\Bmatrix":
|
||||
_convert_and_append_command(r"\rbrace", parent)
|
||||
elif node.token == r"\vmatrix":
|
||||
_convert_and_append_command(r"\vert", parent)
|
||||
elif node.token == r"\Vmatrix":
|
||||
_convert_and_append_command(r"\Vert", parent)
|
||||
elif node.token in (commands.FRAC, commands.GENFRAC) and node.delimiter is not None and node.delimiter[1] != ".":
|
||||
# TODO: use 1.2em if inline
|
||||
_convert_and_append_command(node.delimiter[1], parent, {"minsize": size, "maxsize": size})
|
||||
elif node.token == commands.SKEW and node.attributes is not None:
|
||||
SubElement(parent, "mspace", width="-" + node.attributes["width"])
|
||||
|
||||
|
||||
def _convert_symbol(node: Node, parent: Element, font: Optional[dict[str, Optional[str]]] = None) -> None:
|
||||
token = node.token
|
||||
attributes = node.attributes or {}
|
||||
symbol = convert_symbol(token)
|
||||
if re.match(r"\d+(.\d+)?", token):
|
||||
element = SubElement(parent, "mn", attrib=attributes)
|
||||
element.text = token
|
||||
_set_font(element, element.tag, font)
|
||||
elif token in OPERATORS:
|
||||
element = SubElement(parent, "mo", attrib=attributes)
|
||||
element.text = token if symbol is None else "&#x{};".format(symbol)
|
||||
if token == r"\|":
|
||||
element.attrib["fence"] = "false"
|
||||
if token == r"\smallint":
|
||||
element.attrib["largeop"] = "false"
|
||||
if token in ("(", ")", "[", "]", "|", r"\|", r"\{", r"\}", r"\surd"):
|
||||
element.attrib["stretchy"] = "false"
|
||||
_set_font(element, "fence", font)
|
||||
else:
|
||||
_set_font(element, element.tag, font)
|
||||
elif (
|
||||
symbol
|
||||
and (
|
||||
int(symbol, 16) in range(int("2200", 16), int("22FF", 16) + 1)
|
||||
or int(symbol, 16) in range(int("2190", 16), int("21FF", 16) + 1)
|
||||
)
|
||||
or symbol == "."
|
||||
):
|
||||
element = SubElement(parent, "mo", attrib=attributes)
|
||||
element.text = "&#x{};".format(symbol)
|
||||
_set_font(element, element.tag, font)
|
||||
elif token in (r"\ ", "~", commands.NOBREAKSPACE, commands.SPACE):
|
||||
element = SubElement(parent, "mtext", attrib=attributes)
|
||||
element.text = " "
|
||||
_set_font(element, "mtext", font)
|
||||
elif token == commands.NOT:
|
||||
mpadded = SubElement(parent, "mpadded", width="0")
|
||||
element = SubElement(mpadded, "mtext")
|
||||
element.text = "⧸"
|
||||
elif token in (
|
||||
commands.DETERMINANT,
|
||||
commands.GCD,
|
||||
commands.INTOP,
|
||||
commands.INJLIM,
|
||||
commands.LIMINF,
|
||||
commands.LIMSUP,
|
||||
commands.PR,
|
||||
commands.PROJLIM,
|
||||
):
|
||||
element = SubElement(parent, "mo", attrib={"movablelimits": "true", **attributes})
|
||||
texts = {
|
||||
commands.INJLIM: "inj lim",
|
||||
commands.INTOP: "∫",
|
||||
commands.LIMINF: "lim inf",
|
||||
commands.LIMSUP: "lim sup",
|
||||
commands.PROJLIM: "proj lim",
|
||||
}
|
||||
element.text = texts.get(token, token[1:])
|
||||
_set_font(element, element.tag, font)
|
||||
elif token == commands.IDOTSINT:
|
||||
_parent = SubElement(parent, "mrow", attrib=attributes)
|
||||
for s in ("∫", "⋯", "∫"):
|
||||
element = SubElement(_parent, "mo")
|
||||
element.text = s
|
||||
elif token in (commands.LATEX, commands.TEX):
|
||||
_parent = SubElement(parent, "mrow", attrib=attributes)
|
||||
if token == commands.LATEX:
|
||||
mi_l = SubElement(_parent, "mi")
|
||||
mi_l.text = "L"
|
||||
SubElement(_parent, "mspace", width="-.325em")
|
||||
mpadded = SubElement(_parent, "mpadded", height="+.21ex", depth="-.21ex", voffset="+.21ex")
|
||||
mstyle = SubElement(mpadded, "mstyle", displaystyle="false", scriptlevel="1")
|
||||
mrow = SubElement(mstyle, "mrow")
|
||||
mi_a = SubElement(mrow, "mi")
|
||||
mi_a.text = "A"
|
||||
SubElement(_parent, "mspace", width="-.17em")
|
||||
_set_font(mi_l, mi_l.tag, font)
|
||||
_set_font(mi_a, mi_a.tag, font)
|
||||
mi_t = SubElement(_parent, "mi")
|
||||
mi_t.text = "T"
|
||||
SubElement(_parent, "mspace", width="-.14em")
|
||||
mpadded = SubElement(_parent, "mpadded", height="-.5ex", depth="+.5ex", voffset="-.5ex")
|
||||
mrow = SubElement(mpadded, "mrow")
|
||||
mi_e = SubElement(mrow, "mi")
|
||||
mi_e.text = "E"
|
||||
SubElement(_parent, "mspace", width="-.115em")
|
||||
mi_x = SubElement(_parent, "mi")
|
||||
mi_x.text = "X"
|
||||
_set_font(mi_t, mi_t.tag, font)
|
||||
_set_font(mi_e, mi_e.tag, font)
|
||||
_set_font(mi_x, mi_x.tag, font)
|
||||
elif token.startswith(commands.OPERATORNAME):
|
||||
element = SubElement(parent, "mo", attrib=attributes)
|
||||
element.text = token[14:-1]
|
||||
elif token.startswith(commands.BACKSLASH):
|
||||
element = SubElement(parent, "mi", attrib=attributes)
|
||||
if symbol:
|
||||
element.text = "&#x{};".format(symbol)
|
||||
elif token in commands.FUNCTIONS:
|
||||
element.text = token[1:]
|
||||
else:
|
||||
element.text = token
|
||||
_set_font(element, element.tag, font)
|
||||
else:
|
||||
element = SubElement(parent, "mi", attrib=attributes)
|
||||
element.text = token
|
||||
_set_font(element, element.tag, font)
|
||||
|
||||
|
||||
def _set_font(element: Element, key: str, font: Optional[dict[str, Optional[str]]]) -> None:
|
||||
if font is None:
|
||||
return
|
||||
_font = font[key]
|
||||
if _font is not None:
|
||||
element.attrib["mathvariant"] = _font
|
||||
|
||||
|
||||
def main() -> None: # pragma: no cover
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description="Pure Python library for LaTeX to MathML conversion")
|
||||
parser.add_argument("-V", "--version", dest="version", action="store_true", required=False, help="Show version")
|
||||
parser.add_argument("-b", "--block", dest="block", action="store_true", required=False, help="Display block")
|
||||
|
||||
required = parser.add_argument_group("required arguments")
|
||||
|
||||
group = required.add_mutually_exclusive_group(required=False)
|
||||
group.add_argument("-t", "--text", dest="text", type=str, required=False, help="Text")
|
||||
group.add_argument("-f", "--file", dest="file", type=str, required=False, help="File")
|
||||
group.add_argument("-s", "--stdin", dest="stdin", action="store_true", required=False, help="Stdin")
|
||||
|
||||
arguments = parser.parse_args()
|
||||
display = "block" if arguments.block else "inline"
|
||||
|
||||
if arguments.version:
|
||||
import latex2mathml
|
||||
|
||||
print("latex2mathml", latex2mathml.__version__)
|
||||
elif arguments.text:
|
||||
print(convert(arguments.text, display=display))
|
||||
elif arguments.file:
|
||||
with open(arguments.file) as f:
|
||||
print(convert(f.read(), display=display))
|
||||
elif arguments.stdin:
|
||||
print(convert(sys.stdin.read(), display=display))
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
Reference in New Issue
Block a user