// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT // Package mathml converts TeX mathematics to MathML Core with an engine of // its own, built on the standard library alone. The grammar covers the // standard command surface that maps to MathML: the complete symbol tables, // fractions, scripts, operators with movable limits, stretchy delimiters, // the amsmath environments and bounded macros. // // A construct outside the mappable surface is neither dropped nor // mistranslated: it stays in the output as its verbatim source inside an // merror element, so the author sees exactly what was not understood. package mathml import "strings" // Render converts the TeX source to a MathML Core math element. The block // form sets display="block". The same source always produces // byte-identical output. func Render(source []byte, display bool) []byte { p := newParser(source, display) body := p.parseAll() var b strings.Builder b.WriteString(`") writeNodes(&b, body) b.WriteString("") return []byte(b.String()) } // node is one element of the MathML tree. Attributes keep the order the // parser gave them, which keeps the output deterministic. type node struct { kind string text string attrs []attribute children []*node } type attribute struct { key string value string } func el(kind string, children ...*node) *node { return &node{kind: kind, children: children} } // elA builds an element that carries attributes. func elA(kind string, attrs []attribute, children ...*node) *node { return &node{kind: kind, attrs: attrs, children: children} } func text(kind, text string, attrs ...attribute) *node { return &node{kind: kind, text: text, attrs: attrs} } // errorNode degrades a span of source to its verbatim text in a marked // element. func errorNode(source string) *node { return &node{kind: "merror", children: []*node{{kind: "mtext", text: source}}} } // writeNodes renders nodes without indentation; the output is one line, // which keeps it a single inline unit in the surrounding HTML. func writeNodes(b *strings.Builder, nodes []*node) { for _, n := range nodes { writeNode(b, n) } } func writeNode(b *strings.Builder, n *node) { b.WriteString("<") b.WriteString(n.kind) for _, a := range n.attrs { b.WriteString(" ") b.WriteString(a.key) b.WriteString(`="`) escapeXML(b, a.value) b.WriteString(`"`) } b.WriteString(">") escapeXML(b, n.text) writeNodes(b, n.children) b.WriteString("") } var xmlEscaper = strings.NewReplacer("&", "&", "<", "<", ">", ">", `"`, """) func escapeXML(b *strings.Builder, s string) { xmlEscaper.WriteString(b, s) }