Add MathML math element support to Slipstream#256
Merged
Conversation
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.
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit adds comprehensive MathML (Mathematical Markup Language) support to Slipstream, enabling declarative mathematical notation in HTML documents.
Container Element:
math): Main MathML container with optional display attribute for block or inline renderingToken Elements:
mi): Identifiers (variables, function names) - rendered in italicmo): Operators (+, -, ×, ÷, =, etc.)mn): Numeric literalsmtext): Arbitrary text content for annotationsms): String literals with surrounding quotesLayout Elements:
mrow): Groups sub-expressions togethermfrac): Fractions with numerator/denominator and configurable line thicknessmsup): Superscripts (e.g., x²)msub): Subscripts (e.g., x₀)msubsup): Combined sub/superscriptsmsqrt): Square rootmroot): Nth rootScript Elements:
munder): Underscripts (e.g., limits)mover): Overscripts (e.g., accents)munderover): Combined under/overscripts (e.g., summations)Table Elements:
mtable): Tables for matrices and systems of equationsmtr): Table rowsmtd): Table cellsAll 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