Skip to content

XSLT stylesheets compiled to C# by a source generator: Native AOT - #5

Open
simonebortolin wants to merge 4 commits into
mainfrom
claude/xslt-generator
Open

simonebortolin wants to merge 4 commits into
mainfrom
claude/xslt-generator

Conversation

@simonebortolin

Copy link
Copy Markdown
Owner

What

SharpEquations.XsltGenerator, a new source generator, compiles the three XSLT 1.0 stylesheets of Resources/ (omml2mml.xsl, mml2omml.xsl, mtef2mml.xsl) to C# at build time. XslCompiledTransform, which emits IL at run time, is no longer used, so the library is IsAotCompatible on net10.0 and runs under Native AOT.

  • Generator (src/SharpEquations.XsltGenerator/): it generates a method for each template, a lazy property for each global variable and, for each mode, the dispatch of xsl:apply-templates. The dispatch picks templates by element name first and follows the XSLT priority rules. XPath expressions become typed C# when their type is known, and object otherwise. Anything the stylesheets don't use (keys, sort, number, imports, extension functions) is a build error, XSLT001.
  • Runtime (src/SharpEquations/Internal/Xslt/): the XPath data model over LINQ to XML, the XPath functions, and the result tree. The output matches what XslCompiledTransform gave once its text was parsed again: disable-output-escaping is treated as markup (mtef2mml writes character references in pieces), and empty text gives <t></t>.
  • Declarations: Stylesheets.cs declares the generated classes as partial, so that Parlot, which compiles the project without the other generators, still compiles.
  • Resources: the stylesheets are no longer embedded in the assembly.

Verification

  • Xslt/CompiledStylesheetTests compares the compiled stylesheets with XslCompiledTransform on 2,122 inputs: the OMML of Word documents and of the UnicodeMathML examples, their MathML, and the MTEF fixtures. Every input without characters outside the BMP gives the same result.
  • All tests pass on net10.0 and net481 (5,274 tests).
  • I published a Native AOT test app with no warnings. In it, OMML in both directions and MTEF convert correctly.

Behavior change

Strings are counted by code points, as XPath specifies; XslCompiledTransform counted UTF-16 units. In practice only characters outside the BMP that have no plain-letter form (emoji) are written differently in OMML. A single 🍕 is now treated as one character, and so loses m:sty p. Math alphanumerics such as 𝒢 and 𝟏 are unaffected, because OmmlSerializer writes them as plain letters with m:scr/m:sty before the stylesheet runs. The tests on branch claude/astral-omml-tests-ae8c6b pin the current behavior and are not part of this PR.

Costs

The first version is about 2–2.5× slower than XslCompiledTransform on OMML, with about 3× the allocations: XPath steps become lists and LINQ. Making it faster is planned as follow-up work.

VersionPrefix 1.0.3 → 1.1.0.

🤖 Generated with Claude Code

SharpEquations.XsltGenerator compiles the three XSLT 1.0 stylesheets of
Resources (omml2mml, mml2omml, mtef2mml) to C# at build time: a method for
each template, the dispatch of apply-templates by element name, the XPath
expressions as typed C# over Internal/Xslt (the XPath data model over LINQ to
XML, and the result tree). XslCompiledTransform, which emits IL at run time,
is gone: the library is IsAotCompatible on net10.0 and runs under Native AOT,
and the stylesheets are no longer embedded.

The output is what XslCompiledTransform gave, as text parsed again
(disable-output-escaping as markup, empty text as <t></t>);
CompiledStylesheetTests compares the two on the corpora. Strings are counted
by code points, as XPath defines them, where XslCompiledTransform counted
UTF-16 units: only characters outside the basic plane without a plain-letter
form (emoji) are written differently in OMML.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Pre-release 1.1.0-pr.5.100 is ready: https://github.com/simonebortolin/SharpEquations/releases/tag/pr-5-100-1

NuGet package of 788f2bb. Updated on every push, deleted when this PR is closed.

@simonebortolin
simonebortolin added this pull request to stack #25 November 9, 2026 19:43
@simonebortolin
simonebortolin force-pushed the claude/xslt-generator branch 2 times, most recently from bddcb10 to 169aab4 Compare November 11, 2026 00:03
@simonebortolin
simonebortolin removed this pull request from stack #25 November 11, 2026 00:08
@simonebortolin
simonebortolin added this pull request to stack #34 November 11, 2026 00:08
ctop.xsl, merged from main, is compiled by the generator, but its Transform had no
defining declaration (CS0759): it is declared with the others and EquationStylesheets
runs it. The emoji tests of OmmlAstralTests pinned the UTF-16 counting of
XslCompiledTransform; the generated stylesheets count code points as XPath does, so
🍕 is written italic, in one run, and reads back to the same equation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyVioBuiMiSScbUYtdXLXq

This branch has not been deployed

No deployments
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.

2 participants