Skip to content

Comments

Add MathML math element support to Slipstream#256

Merged
jverkoey merged 10 commits intomainfrom
claude/add-mathml-support-011CUthkm58Z4N68PG7oqSDv
Nov 8, 2025
Merged

Add MathML math element support to Slipstream#256
jverkoey merged 10 commits intomainfrom
claude/add-mathml-support-011CUthkm58Z4N68PG7oqSDv

Conversation

@jverkoey
Copy link
Collaborator

@jverkoey jverkoey commented Nov 7, 2025

This commit adds comprehensive MathML (Mathematical Markup Language) support to Slipstream, enabling declarative mathematical notation in HTML documents.

Container Element:

  • Math (math): Main MathML container with optional display attribute for block or inline rendering

Token Elements:

  • MI (mi): Identifiers (variables, function names) - rendered in italic
  • MO (mo): Operators (+, -, ×, ÷, =, etc.)
  • MN (mn): Numeric literals
  • MText (mtext): Arbitrary text content for annotations
  • MS (ms): String literals with surrounding quotes

Layout Elements:

  • MRow (mrow): Groups sub-expressions together
  • MFrac (mfrac): Fractions with numerator/denominator and configurable line thickness
  • MSup (msup): Superscripts (e.g., x²)
  • MSub (msub): Subscripts (e.g., x₀)
  • MSubSup (msubsup): Combined sub/superscripts
  • MSqrt (msqrt): Square root
  • MRoot (mroot): Nth root

Script Elements:

  • MUnder (munder): Underscripts (e.g., limits)
  • MOver (mover): Overscripts (e.g., accents)
  • MUnderOver (munderover): Combined under/overscripts (e.g., summations)

Table Elements:

  • MTable (mtable): Tables for matrices and systems of equations
  • MTr (mtr): Table rows
  • MTd (mtd): Table cells

All elements follow Slipstream patterns with SwiftUI-style ViewBuilder APIs while honoring W3C MathML terminology. Comprehensive test coverage includes simple expressions, complex formulas (quadratic formula, summations), matrices, and nested structures.

Also updates SlipstreamForWebDevelopers.md to document all MathML elements and adds missing SVG documentation.

Closes #25

This commit adds comprehensive MathML (Mathematical Markup Language) support
to Slipstream, enabling declarative mathematical notation in HTML documents.

**Container Element:**
- **Math** (`math`): Main MathML container with optional display attribute
  for block or inline rendering

**Token Elements:**
- **MI** (`mi`): Identifiers (variables, function names) - rendered in italic
- **MO** (`mo`): Operators (+, -, ×, ÷, =, etc.)
- **MN** (`mn`): Numeric literals
- **MText** (`mtext`): Arbitrary text content for annotations
- **MS** (`ms`): String literals with surrounding quotes

**Layout Elements:**
- **MRow** (`mrow`): Groups sub-expressions together
- **MFrac** (`mfrac`): Fractions with numerator/denominator and configurable
  line thickness
- **MSup** (`msup`): Superscripts (e.g., x²)
- **MSub** (`msub`): Subscripts (e.g., x₀)
- **MSubSup** (`msubsup`): Combined sub/superscripts
- **MSqrt** (`msqrt`): Square root
- **MRoot** (`mroot`): Nth root

**Script Elements:**
- **MUnder** (`munder`): Underscripts (e.g., limits)
- **MOver** (`mover`): Overscripts (e.g., accents)
- **MUnderOver** (`munderover`): Combined under/overscripts (e.g., summations)

**Table Elements:**
- **MTable** (`mtable`): Tables for matrices and systems of equations
- **MTr** (`mtr`): Table rows
- **MTd** (`mtd`): Table cells

All elements follow Slipstream patterns with SwiftUI-style ViewBuilder APIs
while honoring W3C MathML terminology. Comprehensive test coverage includes
simple expressions, complex formulas (quadratic formula, summations),
matrices, and nested structures.

Also updates SlipstreamForWebDevelopers.md to document all MathML elements
and adds missing SVG documentation.
@jverkoey jverkoey enabled auto-merge (squash) November 7, 2025 15:02
The 'subscript' keyword is reserved in Swift and cannot be used as a
function name, causing compilation errors.
Changed MI, MO, MN, MText, and MS elements to use direct text rendering
instead of W3CElement protocol. This ensures text content is rendered
inline (e.g., <mi>x</mi>) rather than with extra newlines and formatting.

The token elements now follow the same pattern as SVGText, using
element.text() to set content directly, which produces cleaner HTML
output that matches test expectations.
Changed MI, MO, MN, MText, and MS to use element.appendText() instead
of element.text() to ensure inline rendering. The appendText() method is
the same one used by DOMString and properly keeps text content inline
without extra newlines.
Added MathML token elements (mi, mo, mn, ms, mtext) to SwiftSoup's
inline tags list in the renderHTML and inlineHTML functions. This tells
SwiftSoup's formatter to treat these elements as inline, preventing the
addition of newlines and indentation around their text content.

This ensures MathML expressions render compactly:
- Before: <mi>\n x\n</mi>
- After: <mi>x</mi>

The configuration is applied to both renderHTML and inlineHTML functions
to ensure consistent formatting across all rendering contexts.
Since SwiftSoup doesn't recognize MathML tags, it formats them as
block-level elements with extra newlines and indentation. This commit
adds post-processing to the renderHTML and inlineHTML functions to
collapse whitespace within MathML token elements (mi, mo, mn, ms, mtext).

The regex pattern matches <tag>\s*text\s*</tag> and replaces it with
<tag>text</tag>, removing the unwanted formatting while preserving the
proper indentation of the surrounding structure.

This approach was necessary because SwiftSoup doesn't provide an API to
register custom inline tags, and its Tag system uses a private known tags
dictionary that can't be modified at runtime.
Updated the regex pattern to only remove newlines and indentation added
by SwiftSoup's pretty-printer, while preserving spaces that are part of
the actual text content.

Changed from \s* (which matches all whitespace including content spaces)
to \r?\n[ \t]+ which specifically matches:
- Newline after opening tag
- Indentation (spaces/tabs only)
- Then captures content including any leading/trailing spaces
- Removes newline and indentation before closing tag

This fixes the test failure where MText(" inches") was being rendered
as <mtext>inches</mtext> instead of <mtext> inches</mtext>.
Switched from simple regex replacement to NSRegularExpression with custom
logic to correctly handle MathML token element formatting. The new approach:

1. Captures leading spaces, content, and closing tag indentation separately
2. Uses the closing tag's indentation to determine how many characters to strip
3. Combines leading spaces + content, then strips only the indentation amount
4. Preserves any spaces beyond the indentation (those are content spaces)

This correctly handles cases like MText(" inches") where the leading space
is part of the content and must be preserved, while still removing the
SwiftSoup pretty-printer's formatting indentation.

Example:
Input:  <mtext>\n   inches\n  </mtext> (3 spaces + content, 2 space indent)
Output: <mtext> inches</mtext> (1 content space preserved)
The nsString variable is reassigned in the loop after each replacement,
so it needs to be declared as var instead of let.
SwiftSoup indents content one level deeper than its parent element.
For example:
  <mi>      (2 spaces)
   a        (3 spaces - one level deeper)
  </mi>     (2 spaces)

The previous logic only stripped the closing tag's indentation (2),
leaving an extra space. The fix is to strip closingIndent + 1 to
account for the content being nested one level deeper.

This correctly handles both:
- MI("a") -> <mi>a</mi> (no extra space)
- MText(" inches") -> <mtext> inches</mtext> (content space preserved)
@jverkoey jverkoey merged commit dce8999 into main Nov 8, 2025
2 checks passed
@jverkoey jverkoey deleted the claude/add-mathml-support-011CUthkm58Z4N68PG7oqSDv branch November 8, 2025 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement the full suite of W3C elements

2 participants