/************************************************************* * * Copyright (c) 2021-2025 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file Implements the elementary MathML3 support (experimental) * using David Carlisle's XLST transform. * * @author dpvc@mathjax.org (Davide Cervone) */ import { MathItem } from '../../../core/MathItem.js'; import { MathDocument } from '../../../core/MathDocument.js'; import { Handler } from '../../../core/Handler.js'; import { OptionList } from '../../../util/Options.js'; import { createTransform } from './mml3-node.js'; import { MathML } from '../../mathml.js'; /** * The data for a MathML prefilter. * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ export type FILTERDATA = { math: MathItem; document: MathDocument; data: N; }; /** * Class that handles XSLT transform for MathML3 elementary math tags. */ export class Mml3 { /** * The XSLT transform as a string; */ public static XSLT: string; // added below (it is huge) /** * The function to convert serialized MathML using the XSLT. * (Different for browser and node environments.) */ protected transform: (node: N, doc: MathDocument) => N; /** * @param {MathDocument} document The MathDocument for the transformation * @class */ constructor(document: MathDocument) { if (typeof XSLTProcessor === 'undefined') { // // For Node, get the trasnform from the external module // this.transform = createTransform(); } else { // // For in-browser use, use the browser's XSLTProcessor // const processor = new XSLTProcessor(); const parsed = document.adaptor.parse( Mml3.XSLT, 'text/xml' ) as any as Node; processor.importStylesheet(parsed); this.transform = (node: N) => { const adaptor = document.adaptor; const div = adaptor.node('div', {}, [adaptor.clone(node)]); const dom = adaptor.parse(adaptor.serializeXML(div), 'text/xml'); const mml = processor.transformToDocument( dom as any as Node ) as any as N; return mml ? adaptor.tags(mml, 'math')[0] : node; }; } } /** * The mathml filter for the MathML input jax * * @param {FILTERDATA} args The data from the pre-filter chain. */ public mmlFilter(args: FILTERDATA) { if (args.document.options.enableMml3) { args.data = this.transform(args.data, args.document); } } } /** * Add Mml3 support into the handler. * * @param {Handler} handler The current handler. * @returns {Handler} The provided handler for pipelining. */ export function Mml3Handler( handler: Handler ): Handler { handler.documentClass = class extends handler.documentClass { /** * @override */ public static OPTIONS: OptionList = { ...handler.documentClass.OPTIONS, enableMml3: true, }; /** * Add a prefilter to the MathML input jax, if there is one. * * @override * @class */ constructor(...args: any[]) { super(...args); for (const jax of this.inputJax || []) { if (jax.name === 'MathML') { if (!jax.options._mml3) { // prevent filter being added twice (e.g., when a11y tools load) const mml3 = new Mml3(this); (jax as MathML).mmlFilters.add(mml3.mmlFilter.bind(mml3)); jax.options._mml3 = true; } break; } } } }; return handler; } // // The actual XSLT transform // Mml3.XSLT = ` ltr ) ( ] [ } { ) ( ] [ } { \ ) ( } { > < top right 0 decimalpoint decimalpoint . decimalpoint * 0.1em 0.15em 0.2em 0.15em 0 ) ( / \\ : = top ) `;