/************************************************************* * * Copyright (c) 2022-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 Configuration file for the texhtml package. * * @author v.sorge@mathjax.org (Volker Sorge) */ import { HandlerType, ConfigurationType } from '../HandlerTypes.js'; import { Configuration } from '../Configuration.js'; import TexParser from '../TexParser.js'; import { MacroMap } from '../TokenMap.js'; import { ParseMethod } from '../Types.js'; import ParseOptions from '../ParseOptions.js'; import TexError from '../TexError.js'; import { HTMLDocument } from '../../../handlers/html/HTMLDocument.js'; import { HtmlNode } from '../../../core/MmlTree/MmlNodes/HtmlNode.js'; import { HTMLDomStrings } from '../../../handlers/html/HTMLDomStrings.js'; import { DOMAdaptor } from '../../../core/DOMAdaptor.js'; export const HtmlNodeMethods: { [key: string]: ParseMethod } = { /** * Handle a serialized tag * * @param {TexParser} parser The active TeX parser * @param {string} _name The name of the macro * @returns {boolean} Success value */ TexHTML(parser: TexParser, _name: string): boolean { // // Check that is allowed and that that is what we have // if (!parser.options.allowTexHTML) return false; const match = parser.string .slice(parser.i) .match(/^tex-html(?: n="(\d+)")?>/); if (!match) return false; // // Get the serialized HTML that contains // (If there are nested , they are marked using an n="x" attribute and // an associated comment that marks which goes with it.) // parser.i += match[0].length; const end = (match[1] ? `` : '') + ''; const i = parser.string.slice(parser.i).indexOf(end); if (i < 0) { throw new TexError( 'TokenNotFoundForCommand', 'Could not find %1 for %2', end, '<' + match[0] ); } const html = parser.string.substring(parser.i, parser.i + i).trim(); parser.i += i + 11 + (match[1] ? 3 + match[1].length : 0); // // Parse the HTML and check that there is something there // const adaptor = parser.configuration.packageData.get('texhtml').adaptor; const nodes = adaptor.childNodes(adaptor.body(adaptor.parse(html))); if (nodes.length === 0) return true; // // Create an mtext element containing the HtmlNode internal node and push it. // const DOM = nodes.length === 1 ? nodes[0] : adaptor.node('div', {}, nodes); const node = (parser.create('node', 'html') as HtmlNode).setHTML( DOM, adaptor ); parser.Push(parser.create('node', 'mtext', [node])); return true; }, }; new MacroMap('tex-html', { '<': HtmlNodeMethods.TexHTML }); export const TexHtmlConfiguration = Configuration.create('texhtml', { [ConfigurationType.HANDLER]: { [HandlerType.CHARACTER]: ['tex-html'], }, [ConfigurationType.OPTIONS]: { allowTexHTML: false, // Must turn this on explicitly, since it allows unfiltered HTML insertion. }, [ConfigurationType.CONFIG]: () => { if (HTMLDomStrings) { // // Add the needed includeHtmlTags definition to handle tags // const include = HTMLDomStrings.OPTIONS.includeHtmlTags; if (!include['tex-html']) { include['tex-html'] = ( node: any, adaptor: DOMAdaptor ) => { // // Use the node's serialized contents, and check if there are // nested nodes; if so, mark the one that we are using // so that we match the correct one when we look for it later // (such comments are removed from the DOM by the browser, so can't // be spoofed). // const html = adaptor.innerHTML(node); const n = html.split(/<\/tex-html>/).length; const N = n > 1 ? ` n="${n}"` : ''; return ( `` + html + (n > 1 ? `` : '') + `` ); }; } } }, [ConfigurationType.PREPROCESSORS]: [ (data: { document: HTMLDocument; data: ParseOptions }) => { data.data.packageData.set('texhtml', { adaptor: data.document.adaptor }); // save the DOMadaptor }, ], [ConfigurationType.POSTPROCESSORS]: [ (data: { data: ParseOptions }) => { data.data.packageData.set('texhtml', { adaptor: null }); // clear the DOMadaptor }, ], });