Skip to content

rawHtml: raw MathML children of an MDX JSX <math> element reparse in HTML content #285

Description

@gtritchie

Split out from #283 (the fix for #249), where it was noted as a follow-up. Same family as #249, for the other foreign-content namespace.

What happens

With rawHtml enabled, reparse_children_into (crates/satteri-ast/src/hast/from_html.rs) reparses an MDX JSX element's children in an <svg> fragment context when the element is named svg (#283), and in the HTML <template> context otherwise. A JSX element named math falls into the second case, so raw MathML under it is read as HTML: /> means nothing on an unknown HTML element, so <mspace width="1em"/> stays open and swallows the following siblings. Inside a real raw <math> html5ever enters the MathML namespace and handles the self-closing tag.

Attribute names are unaffected: MathML attributes use the HTML schema in property-information and here, so unlike #249 this is structural only (self-closing, nesting, and the MathML text/HTML integration points).

Repro

npx tsx repro.mts from packages/satteri:

import { mdxToJs, markdownToHast, defineMdastPlugin } from "satteri";

const inject = defineMdastPlugin({
  name: "inject",
  paragraph() {
    return { type: "html", value: '<mspace width="1em"/><mi>x</mi>' };
  },
});

console.log(
  mdxToJs("<math>\n\ntext\n\n</math>\n", { mdastPlugins: [inject], features: { rawHtml: true } }).code,
);
console.log(JSON.stringify(markdownToHast('<math><mspace width="1em"/><mi>x</mi></math>', { features: { rawHtml: true } })));

Output (main and #283):

_jsx(_components.math, { children: _jsx(_components.mspace, {
    width: "1em",
    children: _jsx(_components.mi, { children: "x" })
}) })

versus the same markup inside raw <math>, where mspace and mi are siblings:

{"tagName":"math","children":[{"tagName":"mspace","properties":{"width":"1em"},"children":[]},{"tagName":"mi","children":[{"type":"text","value":"x"}]}]}

Reach

Not reachable from MDX markup alone (HTML-looking syntax is JSX there). It needs a plugin-injected html node as a child of a JSX <math> element, compiled with features: { rawHtml: true }.

Sketch of a fix

The narrow version mirrors #283: add HtmlSpace::MathMl with a MathML-namespace <math> context element, and enter it for a JSX element named math. But the context is currently a single in_svg: bool threaded through EmitTask, emit, emit_arena_node, and reparse_children_into, and a second namespace does not fit a bool. The shape that scales is to thread the enclosing element's QualName instead (for a stitch, the parsed parent's own name; for a JSX element, its name in the inherited namespace; <template> for component names), hand it to parse_fragment as the context element, and derive in_svg for the serializer from it. That also lets html5ever decide the integration points (foreignObject, annotation-xml, MathML text integration points) instead of the hand-written is_html_integration_point list from #283.

Test coverage

None: the from_html.rs reparse tests and the rawHtml conformance cases have no MathML-under-MDX case; the existing mathml conformance case has the <math> tag inside the raw text itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions