diff --git a/public/react/src/common/TextUtil.js b/public/react/src/common/TextUtil.js index a0140fc85..1c87badb4 100644 --- a/public/react/src/common/TextUtil.js +++ b/public/react/src/common/TextUtil.js @@ -1,7 +1,7 @@ import { bytesToSize, getUrl, getUrl2 } from 'educoder'; const $ = window.$ import showdown from 'showdown' -import showdownKatex from 'showdown-katex' +import showdownKatex from './showdown-katex/showdown-katex' export function isImageExtension(fileName) { return fileName ? !!(fileName.match(/.(jpg|jpeg|png|gif)$/i)) : false diff --git a/public/react/src/common/showdown-katex/asciimath-to-tex.js b/public/react/src/common/showdown-katex/asciimath-to-tex.js new file mode 100644 index 000000000..120c619df --- /dev/null +++ b/public/react/src/common/showdown-katex/asciimath-to-tex.js @@ -0,0 +1,2348 @@ +/**! + * @preserve +Based on ASCIIMathTeXImg.js but now part of https://github.com/obedm503/showdown-katex + +Based on ASCIIMathML, Version 1.4.7 Aug 30, 2005, (c) Peter Jipsen http://www.chapman.edu/~jipsen +Modified with TeX conversion for IMG rendering Sept 6, 2006 (c) David Lippman http://www.pierce.ctc.edu/dlippman + Updated to match ver 2.2 Mar 3, 2014 + Latest at https://github.com/mathjax/asciimathml + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the 'Software'), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// token types +const tokens = { + CONST: 0, + UNARY: 1, + BINARY: 2, + INFIX: 3, + LEFTBRACKET: 4, + RIGHTBRACKET: 5, + SPACE: 6, + UNDEROVER: 7, + DEFINITION: 8, + LEFTRIGHT: 9, + TEXT: 10, +}; + +const quoteSymbol = { + input: "'", + tag: 'mtext', + output: 'mbox', + tex: null, + ttype: tokens.TEXT, +}; + +const symbols = [ + // some greek symbols + { + input: 'alpha', + tag: 'mi', + output: '\u03B1', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'beta', + tag: 'mi', + output: '\u03B2', + tex: null, + ttype: tokens.CONST, + }, + { input: 'chi', tag: 'mi', output: '\u03C7', tex: null, ttype: tokens.CONST }, + { + input: 'delta', + tag: 'mi', + output: '\u03B4', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'Delta', + tag: 'mo', + output: '\u0394', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'epsi', + tag: 'mi', + output: '\u03B5', + tex: 'epsilon', + ttype: tokens.CONST, + }, + { + input: 'varepsilon', + tag: 'mi', + output: '\u025B', + tex: null, + ttype: tokens.CONST, + }, + { input: 'eta', tag: 'mi', output: '\u03B7', tex: null, ttype: tokens.CONST }, + { + input: 'gamma', + tag: 'mi', + output: '\u03B3', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'Gamma', + tag: 'mo', + output: '\u0393', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'iota', + tag: 'mi', + output: '\u03B9', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'kappa', + tag: 'mi', + output: '\u03BA', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'lambda', + tag: 'mi', + output: '\u03BB', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'Lambda', + tag: 'mo', + output: '\u039B', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'lamda', + tag: 'mi', + output: 'lambda', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: 'Lamda', + tag: 'mi', + output: 'Lambda', + tex: null, + ttype: tokens.DEFINITION, + }, + { input: 'mu', tag: 'mi', output: '\u03BC', tex: null, ttype: tokens.CONST }, + { input: 'nu', tag: 'mi', output: '\u03BD', tex: null, ttype: tokens.CONST }, + { + input: 'omega', + tag: 'mi', + output: '\u03C9', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'Omega', + tag: 'mo', + output: '\u03A9', + tex: null, + ttype: tokens.CONST, + }, + { input: 'phi', tag: 'mi', output: '\u03C6', tex: null, ttype: tokens.CONST }, + { + input: 'varphi', + tag: 'mi', + output: '\u03D5', + tex: null, + ttype: tokens.CONST, + }, + { input: 'Phi', tag: 'mo', output: '\u03A6', tex: null, ttype: tokens.CONST }, + { input: 'pi', tag: 'mi', output: '\u03C0', tex: null, ttype: tokens.CONST }, + { input: 'Pi', tag: 'mo', output: '\u03A0', tex: null, ttype: tokens.CONST }, + { input: 'psi', tag: 'mi', output: '\u03C8', tex: null, ttype: tokens.CONST }, + { input: 'Psi', tag: 'mi', output: '\u03A8', tex: null, ttype: tokens.CONST }, + { input: 'rho', tag: 'mi', output: '\u03C1', tex: null, ttype: tokens.CONST }, + { + input: 'sigma', + tag: 'mi', + output: '\u03C3', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'Sigma', + tag: 'mo', + output: '\u03A3', + tex: null, + ttype: tokens.CONST, + }, + { input: 'tau', tag: 'mi', output: '\u03C4', tex: null, ttype: tokens.CONST }, + { + input: 'theta', + tag: 'mi', + output: '\u03B8', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'vartheta', + tag: 'mi', + output: '\u03D1', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'Theta', + tag: 'mo', + output: '\u0398', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'upsilon', + tag: 'mi', + output: '\u03C5', + tex: null, + ttype: tokens.CONST, + }, + { input: 'xi', tag: 'mi', output: '\u03BE', tex: null, ttype: tokens.CONST }, + { input: 'Xi', tag: 'mo', output: '\u039E', tex: null, ttype: tokens.CONST }, + { + input: 'zeta', + tag: 'mi', + output: '\u03B6', + tex: null, + ttype: tokens.CONST, + }, + + // binary operation symbols + { input: '*', tag: 'mo', output: '\u22C5', tex: 'cdot', ttype: tokens.CONST }, + { input: '**', tag: 'mo', output: '\u2217', tex: 'ast', ttype: tokens.CONST }, + { + input: '***', + tag: 'mo', + output: '\u22C6', + tex: 'star', + ttype: tokens.CONST, + }, + { + input: '// ', + tag: 'mo', + output: '/', + tex: '/', + ttype: tokens.CONST, + val: true, + notexcopy: true, + }, + { + input: '\\\\', + tag: 'mo', + output: '\\', + tex: 'backslash', + ttype: tokens.CONST, + }, + { + input: 'setminus', + tag: 'mo', + output: '\\', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'xx', + tag: 'mo', + output: '\u00D7', + tex: 'times', + ttype: tokens.CONST, + }, + { + input: '|><', + tag: 'mo', + output: '\u22C9', + tex: 'ltimes', + ttype: tokens.CONST, + }, + { + input: '><|', + tag: 'mo', + output: '\u22CA', + tex: 'rtimes', + ttype: tokens.CONST, + }, + { + input: '|><|', + tag: 'mo', + output: '\u22C8', + tex: 'bowtie', + ttype: tokens.CONST, + }, + { input: '-:', tag: 'mo', output: '\u00F7', tex: 'div', ttype: tokens.CONST }, + { + input: 'divide', + tag: 'mo', + output: '-:', + tex: null, + ttype: tokens.DEFINITION, + }, + { input: '@', tag: 'mo', output: '\u2218', tex: 'circ', ttype: tokens.CONST }, + { + input: 'o+', + tag: 'mo', + output: '\u2295', + tex: 'oplus', + ttype: tokens.CONST, + }, + { + input: 'ox', + tag: 'mo', + output: '\u2297', + tex: 'otimes', + ttype: tokens.CONST, + }, + { + input: 'o.', + tag: 'mo', + output: '\u2299', + tex: 'odot', + ttype: tokens.CONST, + }, + { + input: 'sum', + tag: 'mo', + output: '\u2211', + tex: null, + ttype: tokens.UNDEROVER, + }, + { + input: 'prod', + tag: 'mo', + output: '\u220F', + tex: null, + ttype: tokens.UNDEROVER, + }, + { + input: '^^', + tag: 'mo', + output: '\u2227', + tex: 'wedge', + ttype: tokens.CONST, + }, + { + input: '^^^', + tag: 'mo', + output: '\u22C0', + tex: 'bigwedge', + ttype: tokens.UNDEROVER, + }, + { input: 'vv', tag: 'mo', output: '\u2228', tex: 'vee', ttype: tokens.CONST }, + { + input: 'vvv', + tag: 'mo', + output: '\u22C1', + tex: 'bigvee', + ttype: tokens.UNDEROVER, + }, + { input: 'nn', tag: 'mo', output: '\u2229', tex: 'cap', ttype: tokens.CONST }, + { + input: 'nnn', + tag: 'mo', + output: '\u22C2', + tex: 'bigcap', + ttype: tokens.UNDEROVER, + }, + { input: 'uu', tag: 'mo', output: '\u222A', tex: 'cup', ttype: tokens.CONST }, + { + input: 'uuu', + tag: 'mo', + output: '\u22C3', + tex: 'bigcup', + ttype: tokens.UNDEROVER, + }, + { + input: 'overset', + tag: 'mover', + output: 'stackrel', + tex: null, + ttype: tokens.BINARY, + }, + { + input: 'underset', + tag: 'munder', + output: 'stackrel', + tex: null, + ttype: tokens.BINARY, + }, + + // binary relation symbols + { input: '!=', tag: 'mo', output: '\u2260', tex: 'ne', ttype: tokens.CONST }, + { input: ':=', tag: 'mo', output: ':=', tex: null, ttype: tokens.CONST }, + { input: 'lt', tag: 'mo', output: '<', tex: null, ttype: tokens.CONST }, + { input: 'gt', tag: 'mo', output: '>', tex: null, ttype: tokens.CONST }, + { input: '<=', tag: 'mo', output: '\u2264', tex: 'le', ttype: tokens.CONST }, + { + input: 'lt=', + tag: 'mo', + output: '\u2264', + tex: 'leq', + ttype: tokens.CONST, + }, + { + input: 'gt=', + tag: 'mo', + output: '\u2265', + tex: 'geq', + ttype: tokens.CONST, + }, + { input: '>=', tag: 'mo', output: '\u2265', tex: 'ge', ttype: tokens.CONST }, + { + input: '-<', + tag: 'mo', + output: '\u227A', + tex: 'prec', + ttype: tokens.CONST, + }, + { input: '-lt', tag: 'mo', output: '\u227A', tex: null, ttype: tokens.CONST }, + { + input: '>-', + tag: 'mo', + output: '\u227B', + tex: 'succ', + ttype: tokens.CONST, + }, + { + input: '-<=', + tag: 'mo', + output: '\u2AAF', + tex: 'preceq', + ttype: tokens.CONST, + }, + { + input: '>-=', + tag: 'mo', + output: '\u2AB0', + tex: 'succeq', + ttype: tokens.CONST, + }, + { input: 'in', tag: 'mo', output: '\u2208', tex: null, ttype: tokens.CONST }, + { + input: '!in', + tag: 'mo', + output: '\u2209', + tex: 'notin', + ttype: tokens.CONST, + }, + { + input: 'sub', + tag: 'mo', + output: '\u2282', + tex: 'subset', + ttype: tokens.CONST, + }, + { + input: 'sup', + tag: 'mo', + output: '\u2283', + tex: 'supset', + ttype: tokens.CONST, + }, + { + input: 'sube', + tag: 'mo', + output: '\u2286', + tex: 'subseteq', + ttype: tokens.CONST, + }, + { + input: 'supe', + tag: 'mo', + output: '\u2287', + tex: 'supseteq', + ttype: tokens.CONST, + }, + { + input: '-=', + tag: 'mo', + output: '\u2261', + tex: 'equiv', + ttype: tokens.CONST, + }, + { + input: '~=', + tag: 'mo', + output: '\u2245', + tex: 'stackrel{\\sim}{=}', + ttype: tokens.CONST, + }, // back hack b/c mimetex doesn't support /cong + { + input: 'cong', + tag: 'mo', + output: '~=', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: '~~', + tag: 'mo', + output: '\u2248', + tex: 'approx', + ttype: tokens.CONST, + }, + { + input: 'prop', + tag: 'mo', + output: '\u221D', + tex: 'propto', + ttype: tokens.CONST, + }, + + // logical symbols + { input: 'and', tag: 'mtext', output: 'and', tex: null, ttype: tokens.SPACE }, + { input: 'or', tag: 'mtext', output: 'or', tex: null, ttype: tokens.SPACE }, + { + input: 'not', + tag: 'mo', + output: '\u00AC', + tex: 'neg', + ttype: tokens.CONST, + }, + { + input: '=>', + tag: 'mo', + output: '\u21D2', + tex: 'Rightarrow', + ttype: tokens.CONST, + }, + { + input: 'implies', + tag: 'mo', + output: '=>', + tex: null, + ttype: tokens.DEFINITION, + }, + { input: 'if', tag: 'mo', output: 'if', tex: null, ttype: tokens.SPACE }, + { + input: '<=>', + tag: 'mo', + output: '\u21D4', + tex: 'Leftrightarrow', + ttype: tokens.CONST, + }, + { + input: 'iff', + tag: 'mo', + output: '<=>', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: 'AA', + tag: 'mo', + output: '\u2200', + tex: 'forall', + ttype: tokens.CONST, + }, + { + input: 'EE', + tag: 'mo', + output: '\u2203', + tex: 'exists', + ttype: tokens.CONST, + }, + { + input: '_|_', + tag: 'mo', + output: '\u22A5', + tex: 'bot', + ttype: tokens.CONST, + }, + { input: 'TT', tag: 'mo', output: '\u22A4', tex: 'top', ttype: tokens.CONST }, + { + input: '|--', + tag: 'mo', + output: '\u22A2', + tex: 'vdash', + ttype: tokens.CONST, + }, + { + input: '|==', + tag: 'mo', + output: '\u22A8', + tex: 'models', + ttype: tokens.CONST, + }, // mimetex doesn't support + + // grouping brackets + { + input: '(', + tag: 'mo', + output: '(', + tex: null, + ttype: tokens.LEFTBRACKET, + val: true, + }, + { + input: ')', + tag: 'mo', + output: ')', + tex: null, + ttype: tokens.RIGHTBRACKET, + val: true, + }, + { + input: '[', + tag: 'mo', + output: '[', + tex: null, + ttype: tokens.LEFTBRACKET, + val: true, + }, + { + input: ']', + tag: 'mo', + output: ']', + tex: null, + ttype: tokens.RIGHTBRACKET, + val: true, + }, + { + input: '{', + tag: 'mo', + output: '{', + tex: 'lbrace', + ttype: tokens.LEFTBRACKET, + }, + { + input: '}', + tag: 'mo', + output: '}', + tex: 'rbrace', + ttype: tokens.RIGHTBRACKET, + }, + { + input: '|', + tag: 'mo', + output: '|', + tex: null, + ttype: tokens.LEFTRIGHT, + val: true, + }, + // {input:'||', tag:'mo', output:'||', tex:null, ttype:LEFTRIGHT}, + { + input: '(:', + tag: 'mo', + output: '\u2329', + tex: 'langle', + ttype: tokens.LEFTBRACKET, + }, + { + input: ':)', + tag: 'mo', + output: '\u232A', + tex: 'rangle', + ttype: tokens.RIGHTBRACKET, + }, + { + input: '<<', + tag: 'mo', + output: '\u2329', + tex: 'langle', + ttype: tokens.LEFTBRACKET, + }, + { + input: '>>', + tag: 'mo', + output: '\u232A', + tex: 'rangle', + ttype: tokens.RIGHTBRACKET, + }, + { + input: '{:', + tag: 'mo', + output: '{:', + tex: null, + ttype: tokens.LEFTBRACKET, + invisible: true, + }, + { + input: ':}', + tag: 'mo', + output: ':}', + tex: null, + ttype: tokens.RIGHTBRACKET, + invisible: true, + }, + + // miscellaneous symbols + { input: 'int', tag: 'mo', output: '\u222B', tex: null, ttype: tokens.CONST }, + { + input: 'dx', + tag: 'mi', + output: '{:d x:}', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: 'dy', + tag: 'mi', + output: '{:d y:}', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: 'dz', + tag: 'mi', + output: '{:d z:}', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: 'dt', + tag: 'mi', + output: '{:d t:}', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: 'oint', + tag: 'mo', + output: '\u222E', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'del', + tag: 'mo', + output: '\u2202', + tex: 'partial', + ttype: tokens.CONST, + }, + { + input: 'grad', + tag: 'mo', + output: '\u2207', + tex: 'nabla', + ttype: tokens.CONST, + }, + { input: '+-', tag: 'mo', output: '\u00B1', tex: 'pm', ttype: tokens.CONST }, + { + input: 'O/', + tag: 'mo', + output: '\u2205', + tex: 'emptyset', + ttype: tokens.CONST, + }, + { + input: 'oo', + tag: 'mo', + output: '\u221E', + tex: 'infty', + ttype: tokens.CONST, + }, + { + input: 'aleph', + tag: 'mo', + output: '\u2135', + tex: null, + ttype: tokens.CONST, + }, + { input: '...', tag: 'mo', output: '...', tex: 'ldots', ttype: tokens.CONST }, + { + input: ':.', + tag: 'mo', + output: '\u2234', + tex: 'therefore', + ttype: tokens.CONST, + }, + { + input: ":'", + tag: 'mo', + output: '\u2235', + tex: 'because', + ttype: tokens.CONST, + }, + { + input: '/_', + tag: 'mo', + output: '\u2220', + tex: 'angle', + ttype: tokens.CONST, + }, + { + input: '/_\\', + tag: 'mo', + output: '\u25B3', + tex: 'triangle', + ttype: tokens.CONST, + }, + { + input: '\\ ', + tag: 'mo', + output: '\u00A0', + tex: null, + ttype: tokens.CONST, + val: true, + }, + { + input: 'frown', + tag: 'mo', + output: '\u2322', + tex: null, + ttype: tokens.CONST, + }, + { + input: '%', + tag: 'mo', + output: '%', + tex: '%', + ttype: tokens.CONST, + notexcopy: true, + }, + { + input: 'quad', + tag: 'mo', + output: '\u00A0\u00A0', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'qquad', + tag: 'mo', + output: '\u00A0\u00A0\u00A0\u00A0', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'cdots', + tag: 'mo', + output: '\u22EF', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'vdots', + tag: 'mo', + output: '\u22EE', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'ddots', + tag: 'mo', + output: '\u22F1', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'diamond', + tag: 'mo', + output: '\u22C4', + tex: null, + ttype: tokens.CONST, + }, + { + input: 'square', + tag: 'mo', + output: '\u25A1', + tex: 'boxempty', + ttype: tokens.CONST, + }, + { + input: '|__', + tag: 'mo', + output: '\u230A', + tex: 'lfloor', + ttype: tokens.CONST, + }, + { + input: '__|', + tag: 'mo', + output: '\u230B', + tex: 'rfloor', + ttype: tokens.CONST, + }, + { + input: '|~', + tag: 'mo', + output: '\u2308', + tex: 'lceil', + ttype: tokens.CONST, + }, + { + input: 'lceiling', + tag: 'mo', + output: '|~', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: '~|', + tag: 'mo', + output: '\u2309', + tex: 'rceil', + ttype: tokens.CONST, + }, + { + input: 'rceiling', + tag: 'mo', + output: '~|', + tex: null, + ttype: tokens.DEFINITION, + }, + { + input: 'CC', + tag: 'mo', + output: '\u2102', + tex: 'mathbb{C}', + ttype: tokens.CONST, + notexcopy: true, + }, + { + input: 'NN', + tag: 'mo', + output: '\u2115', + tex: 'mathbb{N}', + ttype: tokens.CONST, + notexcopy: true, + }, + { + input: 'QQ', + tag: 'mo', + output: '\u211A', + tex: 'mathbb{Q}', + ttype: tokens.CONST, + notexcopy: true, + }, + { + input: 'RR', + tag: 'mo', + output: '\u211D', + tex: 'mathbb{R}', + ttype: tokens.CONST, + notexcopy: true, + }, + { + input: 'ZZ', + tag: 'mo', + output: '\u2124', + tex: 'mathbb{Z}', + ttype: tokens.CONST, + notexcopy: true, + }, + { + input: 'f', + tag: 'mi', + output: 'f', + tex: null, + ttype: tokens.UNARY, + func: true, + val: true, + }, + { + input: 'g', + tag: 'mi', + output: 'g', + tex: null, + ttype: tokens.UNARY, + func: true, + val: true, + }, + { input: "''", tag: 'mo', output: "''", tex: null, val: true }, + { input: "'''", tag: 'mo', output: "'''", tex: null, val: true }, + { input: "''''", tag: 'mo', output: "''''", tex: null, val: true }, + + // standard functions + { + input: 'lim', + tag: 'mo', + output: 'lim', + tex: null, + ttype: tokens.UNDEROVER, + }, + { + input: 'Lim', + tag: 'mo', + output: 'Lim', + tex: null, + ttype: tokens.UNDEROVER, + }, + { + input: 'sin', + tag: 'mo', + output: 'sin', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'cos', + tag: 'mo', + output: 'cos', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'tan', + tag: 'mo', + output: 'tan', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'arcsin', + tag: 'mo', + output: 'arcsin', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'arccos', + tag: 'mo', + output: 'arccos', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'arctan', + tag: 'mo', + output: 'arctan', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'sinh', + tag: 'mo', + output: 'sinh', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'cosh', + tag: 'mo', + output: 'cosh', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'tanh', + tag: 'mo', + output: 'tanh', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'cot', + tag: 'mo', + output: 'cot', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'coth', + tag: 'mo', + output: 'coth', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'sech', + tag: 'mo', + output: 'sech', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'csch', + tag: 'mo', + output: 'csch', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'sec', + tag: 'mo', + output: 'sec', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'csc', + tag: 'mo', + output: 'csc', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'log', + tag: 'mo', + output: 'log', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'ln', + tag: 'mo', + output: 'ln', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'abs', + tag: 'mo', + output: 'abs', + tex: null, + ttype: tokens.UNARY, + notexcopy: true, + rewriteleftright: ['|', '|'], + }, + { + input: 'norm', + tag: 'mo', + output: 'norm', + tex: null, + ttype: tokens.UNARY, + notexcopy: true, + rewriteleftright: ['\\|', '\\|'], + }, + { + input: 'floor', + tag: 'mo', + output: 'floor', + tex: null, + ttype: tokens.UNARY, + notexcopy: true, + rewriteleftright: ['\\lfloor', '\\rfloor'], + }, + { + input: 'ceil', + tag: 'mo', + output: 'ceil', + tex: null, + ttype: tokens.UNARY, + notexcopy: true, + rewriteleftright: ['\\lceil', '\\rceil'], + }, + { + input: 'Sin', + tag: 'mo', + output: 'sin', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Cos', + tag: 'mo', + output: 'cos', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Tan', + tag: 'mo', + output: 'tan', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Arcsin', + tag: 'mo', + output: 'arcsin', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Arccos', + tag: 'mo', + output: 'arccos', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Arctan', + tag: 'mo', + output: 'arctan', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Sinh', + tag: 'mo', + output: 'sinh', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Sosh', + tag: 'mo', + output: 'cosh', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Tanh', + tag: 'mo', + output: 'tanh', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Cot', + tag: 'mo', + output: 'cot', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Sec', + tag: 'mo', + output: 'sec', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Csc', + tag: 'mo', + output: 'csc', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Log', + tag: 'mo', + output: 'log', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Ln', + tag: 'mo', + output: 'ln', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'Abs', + tag: 'mo', + output: 'abs', + tex: null, + ttype: tokens.UNARY, + notexcopy: true, + rewriteleftright: ['|', '|'], + }, + + { + input: 'det', + tag: 'mo', + output: 'det', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'exp', + tag: 'mo', + output: 'exp', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { input: 'dim', tag: 'mo', output: 'dim', tex: null, ttype: tokens.CONST }, + { + input: 'mod', + tag: 'mo', + output: 'mod', + tex: 'text{mod}', + ttype: tokens.CONST, + notexcopy: true, + }, + { + input: 'gcd', + tag: 'mo', + output: 'gcd', + tex: null, + ttype: tokens.UNARY, + func: true, + }, + { + input: 'lcm', + tag: 'mo', + output: 'lcm', + tex: 'text{lcm}', + ttype: tokens.UNARY, + func: true, + notexcopy: true, + }, + { input: 'lub', tag: 'mo', output: 'lub', tex: null, ttype: tokens.CONST }, + { input: 'glb', tag: 'mo', output: 'glb', tex: null, ttype: tokens.CONST }, + { + input: 'min', + tag: 'mo', + output: 'min', + tex: null, + ttype: tokens.UNDEROVER, + }, + { + input: 'max', + tag: 'mo', + output: 'max', + tex: null, + ttype: tokens.UNDEROVER, + }, + + // arrows + { + input: 'uarr', + tag: 'mo', + output: '\u2191', + tex: 'uparrow', + ttype: tokens.CONST, + }, + { + input: 'darr', + tag: 'mo', + output: '\u2193', + tex: 'downarrow', + ttype: tokens.CONST, + }, + { + input: 'rarr', + tag: 'mo', + output: '\u2192', + tex: 'rightarrow', + ttype: tokens.CONST, + }, + { input: '->', tag: 'mo', output: '\u2192', tex: 'to', ttype: tokens.CONST }, + { + input: '>->', + tag: 'mo', + output: '\u21A3', + tex: 'rightarrowtail', + ttype: tokens.CONST, + }, + { + input: '->>', + tag: 'mo', + output: '\u21A0', + tex: 'twoheadrightarrow', + ttype: tokens.CONST, + }, + { + input: '>->>', + tag: 'mo', + output: '\u2916', + tex: 'twoheadrightarrowtail', + ttype: tokens.CONST, + }, + { + input: '|->', + tag: 'mo', + output: '\u21A6', + tex: 'mapsto', + ttype: tokens.CONST, + }, + { + input: 'larr', + tag: 'mo', + output: '\u2190', + tex: 'leftarrow', + ttype: tokens.CONST, + }, + { + input: 'harr', + tag: 'mo', + output: '\u2194', + tex: 'leftrightarrow', + ttype: tokens.CONST, + }, + { + input: 'rArr', + tag: 'mo', + output: '\u21D2', + tex: 'Rightarrow', + ttype: tokens.CONST, + }, + { + input: 'lArr', + tag: 'mo', + output: '\u21D0', + tex: 'Leftarrow', + ttype: tokens.CONST, + }, + { + input: 'hArr', + tag: 'mo', + output: '\u21D4', + tex: 'Leftrightarrow', + ttype: tokens.CONST, + }, + + // commands with argument + { + input: 'sqrt', + tag: 'msqrt', + output: 'sqrt', + tex: null, + ttype: tokens.UNARY, + }, + { + input: 'root', + tag: 'mroot', + output: 'root', + tex: null, + ttype: tokens.BINARY, + }, + { input: 'frac', tag: 'mfrac', output: '/', tex: null, ttype: tokens.BINARY }, + { input: '/', tag: 'mfrac', output: '/', tex: null, ttype: tokens.INFIX }, + { + input: 'stackrel', + tag: 'mover', + output: 'stackrel', + tex: null, + ttype: tokens.BINARY, + }, + { input: '_', tag: 'msub', output: '_', tex: null, ttype: tokens.INFIX }, + { input: '^', tag: 'msup', output: '^', tex: null, ttype: tokens.INFIX }, + + { + input: 'cancel', + tag: 'menclose', + output: 'cancel', + tex: null, + ttype: tokens.UNARY, + }, + { + input: 'Sqrt', + tag: 'msqrt', + output: 'sqrt', + tex: null, + ttype: tokens.UNARY, + }, + { + input: 'hat', + tag: 'mover', + output: '\u005E', + tex: null, + ttype: tokens.UNARY, + acc: true, + }, + { + input: 'bar', + tag: 'mover', + output: '\u00AF', + tex: 'overline', + ttype: tokens.UNARY, + acc: true, + }, + { + input: 'vec', + tag: 'mover', + output: '\u2192', + tex: null, + ttype: tokens.UNARY, + acc: true, + }, + { + input: 'tilde', + tag: 'mover', + output: '~', + tex: null, + ttype: tokens.UNARY, + acc: true, + }, + { + input: 'dot', + tag: 'mover', + output: '.', + tex: null, + ttype: tokens.UNARY, + acc: true, + }, + { + input: 'ddot', + tag: 'mover', + output: '..', + tex: null, + ttype: tokens.UNARY, + acc: true, + }, + { + input: 'ul', + tag: 'munder', + output: '\u0332', + tex: 'underline', + ttype: tokens.UNARY, + acc: true, + }, + { + input: 'ubrace', + tag: 'munder', + output: '\u23DF', + tex: 'underbrace', + ttype: tokens.UNARY, + acc: true, + }, + { + input: 'obrace', + tag: 'mover', + output: '\u23DE', + tex: 'overbrace', + ttype: tokens.UNARY, + acc: true, + }, + + { + input: 'text', + tag: 'mtext', + output: 'text', + tex: null, + ttype: tokens.TEXT, + }, + { + input: 'mbox', + tag: 'mtext', + output: 'mbox', + tex: null, + ttype: tokens.TEXT, + }, + quoteSymbol, + // { input: 'var', tag: 'mstyle', atname: 'fontstyle', atval: 'italic', output: 'var', tex: null, ttype: tokens.UNARY }, + { input: 'color', tag: 'mstyle', ttype: tokens.BINARY }, + { + input: 'bb', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'bold', + output: 'bb', + tex: 'mathbf', + ttype: tokens.UNARY, + notexcopy: true, + }, + { + input: 'mathbf', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'bold', + output: 'mathbf', + tex: null, + ttype: tokens.UNARY, + }, + { + input: 'sf', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'sans-serif', + output: 'sf', + tex: 'mathsf', + ttype: tokens.UNARY, + notexcopy: true, + }, + { + input: 'mathsf', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'sans-serif', + output: 'mathsf', + tex: null, + ttype: tokens.UNARY, + }, + { + input: 'bbb', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'double-struck', + output: 'bbb', + tex: 'mathbb', + ttype: tokens.UNARY, + notexcopy: true, + }, + { + input: 'mathbb', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'double-struck', + output: 'mathbb', + tex: null, + ttype: tokens.UNARY, + }, + { + input: 'cc', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'script', + output: 'cc', + tex: 'mathcal', + ttype: tokens.UNARY, + notexcopy: true, + }, + { + input: 'mathcal', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'script', + output: 'mathcal', + tex: null, + ttype: tokens.UNARY, + }, + { + input: 'tt', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'monospace', + output: 'tt', + tex: 'mathtt', + ttype: tokens.UNARY, + notexcopy: true, + }, + { + input: 'mathtt', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'monospace', + output: 'mathtt', + tex: null, + ttype: tokens.UNARY, + }, + { + input: 'fr', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'fraktur', + output: 'fr', + tex: 'mathfrak', + ttype: tokens.UNARY, + notexcopy: true, + }, + { + input: 'mathfrak', + tag: 'mstyle', + atname: 'mathvariant', + atval: 'fraktur', + output: 'mathfrak', + tex: null, + ttype: tokens.UNARY, + }, +]; + +let inputSymbols = []; + +function refreshSymbols() { + symbols.sort((s1, s2) => (s1.input > s2.input ? 1 : -1)); + inputSymbols = symbols.map(item => item.input); +} + +// function newcommand(oldstr, newstr) { +// symbols.push({ input: oldstr, tag: 'mo', output: newstr, tex: null, ttype: tokens.DEFINITION }); +// refreshSymbols(); +// } + +// function newsymbol(symbolobj) { +// symbols.push(symbolobj); +// refreshSymbols(); +// } + +function removeCharsAndBlanks(str, n) { + // remove n characters and any following blanks + let st; + if ( + str.charAt(n) === '\\' && + str.charAt(n + 1) !== '\\' && + str.charAt(n + 1) !== ' ' + ) { + st = str.slice(n + 1); + } else { + st = str.slice(n); + } + let i = 0; + while (i < st.length && st.charCodeAt(i) <= 32) { + i++; + } + return st.slice(i); +} + +function position(arr, str, n) { + // return position >=n where str appears or would be inserted + // assumes arr is sorted + if (n === 0) { + let len = arr.length; + let m; + n = -1; + while (n + 1 < len) { + m = (n + len) >> 1; + if (arr[m] < str) { + n = m; + } else { + len = m; + } + } + return len; + } + let i = n; + while (i < arr.length && arr[i] < str) { + i++; + } + return i; // i=arr.length || arr[i]>=str +} + +function getSymbol(str) { + // return maximal initial substring of str that appears in names + // return null if there is none + let newPos = 0; // new pos + let oldPos = 0; // old pos + let matchPos; // match pos + let st; + let tagst; + let match = ''; + let more = true; + for (let i = 1; i <= str.length && more; i++) { + st = str.slice(0, i); // initial substring of length i + oldPos = newPos; + newPos = position(inputSymbols, st, oldPos); + if ( + newPos < inputSymbols.length && + str.slice(0, inputSymbols[newPos].length) === inputSymbols[newPos] + ) { + match = inputSymbols[newPos]; + matchPos = newPos; + i = match.length; + } + more = + newPos < inputSymbols.length && + str.slice(0, inputSymbols[newPos].length) >= inputSymbols[newPos]; + } + previousSymbol = currentSymbol; + if (match !== '') { + currentSymbol = symbols[matchPos].ttype; + return symbols[matchPos]; + } + // if str[0] is a digit or - return maxsubstring of digits.digits + currentSymbol = tokens.CONST; + newPos = 1; + st = str.slice(0, 1); + let integ = true; + + while ('0' <= st && st <= '9' && newPos <= str.length) { + st = str.slice(newPos, newPos + 1); + newPos++; + } + if (st === '.') { + st = str.slice(newPos, newPos + 1); + if ('0' <= st && st <= '9') { + integ = false; + newPos++; + while ('0' <= st && st <= '9' && newPos <= str.length) { + st = str.slice(newPos, newPos + 1); + newPos++; + } + } + } + if ((integ && newPos > 1) || newPos > 2) { + st = str.slice(0, newPos - 1); + tagst = 'mn'; + } else { + newPos = 2; + st = str.slice(0, 1); // take 1 character + tagst = ('A' > st || st > 'Z') && ('a' > st || st > 'z') ? 'mo' : 'mi'; + } + if (st === '-' && previousSymbol === tokens.INFIX) { + currentSymbol = tokens.INFIX; + return { + input: st, + tag: tagst, + output: st, + ttype: tokens.UNARY, + func: true, + val: true, + }; + } + return { input: st, tag: tagst, output: st, ttype: tokens.CONST, val: true }; // added val bit +} + +function removeBrackets(node) { + let st; + if (node.charAt(0) === '{' && node.charAt(node.length - 1) === '}') { + let leftchop = 0; + + st = node.substr(1, 5); + if (st === '\\left') { + st = node.charAt(6); + if (st === '(' || st === '[' || st === '{') { + leftchop = 7; + } else { + st = node.substr(6, 7); + if (st === '\\lbrace') { + leftchop = 13; + } + } + } else { + st = node.charAt(1); + if (st === '(' || st === '[') { + leftchop = 2; + } + } + if (leftchop > 0) { + // st = node.charAt(node.length-7); + st = node.substr(node.length - 8); + if (st === '\\right)}' || st === '\\right]}' || st === '\\right.}') { + node = `{${node.substr(leftchop)}`; + node = `${node.substr(0, node.length - 8)}}`; + } else if (st === '\\rbrace}') { + node = `{${node.substr(leftchop)}`; + node = `${node.substr(0, node.length - 14)}}`; + } + } + } + return node; +} + +/* + Parsing ASCII math expressions with the following grammar + v ::= [A-Za-z] | greek letters | numbers | other constant symbols + u ::= sqrt | text | bb | other unary symbols for font commands + b ::= frac | root | stackrel binary symbols + l ::= ( | [ | { | (: | {: left brackets + r ::= ) | ] | } | :) | :} right brackets + S ::= v | lEr | uS | bSS Simple expression + I ::= S_S | S^S | S_S^S | S Intermediate expression + E ::= IE | I/I Expression + Each terminal symbol is translated into a corresponding mathml node. +*/ + +let nestingDepth; +let previousSymbol; +let currentSymbol; + +function getTeXsymbol(symb) { + let pre; + if (typeof symb.val === 'boolean' && symb.val) { + pre = ''; + } else { + pre = '\\'; + } + if (!symb.tex) { + // can't remember why this was here. Breaks /delta /Delta to removed + // return (pre + (pre == '' ? symb.input : symb.input.toLowerCase())); + return pre + symb.input; + } else { + return pre + symb.tex; + } +} + +function parseSexpr(str) { + // parses str and returns [node,tailstr] + let symbol; + let node; + let result; + let i; + // const rightvert = false, + let newFrag = ''; + str = removeCharsAndBlanks(str, 0); + symbol = getSymbol(str); // either a token or a bracket or empty + if (!symbol || (symbol.ttype === tokens.RIGHTBRACKET && nestingDepth > 0)) { + return [null, str]; + } + if (symbol.ttype === tokens.DEFINITION) { + str = symbol.output + removeCharsAndBlanks(str, symbol.input.length); + symbol = getSymbol(str); + } + switch (symbol.ttype) { + case tokens.UNDEROVER: + case tokens.CONST: + str = removeCharsAndBlanks(str, symbol.input.length); + var texsymbol = getTeXsymbol(symbol); + if (texsymbol.charAt(0) === '\\' || symbol.tag === 'mo') { + return [texsymbol, str]; + } else { + return [`{${texsymbol}}`, str]; + } + + case tokens.LEFTBRACKET: // read (expr+) + nestingDepth++; + str = removeCharsAndBlanks(str, symbol.input.length); + + result = parseExpr(str, true); + nestingDepth--; + var leftchop = 0; + if (result[0].substr(0, 6) === '\\right') { + st = result[0].charAt(6); + if (st === ')' || st === ']' || st === '}') { + leftchop = 6; + } else if (st === '.') { + leftchop = 7; + } else { + st = result[0].substr(6, 7); + if (st === '\\rbrace') { + leftchop = 13; + } + } + } + if (leftchop > 0) { + result[0] = result[0].substr(leftchop); + if (symbol.invisible) { + node = `{${result[0]}}`; + } else { + node = `{${getTeXsymbol(symbol)}${result[0]}}`; + } + } else { + if (symbol.invisible) { + node = `{\\left.${result[0]}}`; + } else { + node = `{\\left${getTeXsymbol(symbol)}${result[0]}}`; + } + } + return [node, result[1]]; + case tokens.TEXT: + if (symbol !== quoteSymbol) { + str = removeCharsAndBlanks(str, symbol.input.length); + } + if (str.charAt(0) === '{') { + i = str.indexOf('}'); + } else if (str.charAt(0) === '(') { + i = str.indexOf(')'); + } else if (str.charAt(0) === '[') { + i = str.indexOf(']'); + } else if (symbol === quoteSymbol) { + i = str.slice(1).indexOf("'") + 1; + } else { + i = 0; + } + if (i === -1) { + i = str.length; + } + st = str.slice(1, i); + if (st.charAt(0) === ' ') { + newFrag = '\\ '; + } + newFrag += `\\text{${st}}`; + if (st.charAt(st.length - 1) === ' ') { + newFrag += '\\ '; + } + str = removeCharsAndBlanks(str, i + 1); + return [newFrag, str]; + case tokens.UNARY: + str = removeCharsAndBlanks(str, symbol.input.length); + result = parseSexpr(str); + if (result[0] === null) { + return [`{${getTeXsymbol(symbol)}}`, str]; + } + if (symbol.func === true) { + // functions hack + st = str.charAt(0); + if ( + st === '^' || + st === '_' || + st === '/' || + st === '|' || + st === ',' || + (symbol.input.length === 1 && symbol.input.match(/\w/) && st !== '(') + ) { + return [`{${getTeXsymbol(symbol)}}`, str]; + } else { + node = `{${getTeXsymbol(symbol)}{${result[0]}}}`; + return [node, result[1]]; + } + } + result[0] = removeBrackets(result[0]); + if (symbol.input === 'sqrt') { + // sqrt + return [`\\sqrt{${result[0]}}`, result[1]]; + } else if (symbol.input === 'cancel') { + // cancel + return [`\\cancel{${result[0]}}`, result[1]]; + } else if (typeof symbol.rewriteleftright !== 'undefined') { + // abs, floor, ceil + return [ + `{\\left${symbol.rewriteleftright[0]}${result[0]}\\right${ + symbol.rewriteleftright[1] + }}`, + result[1], + ]; + } else if (symbol.acc === true) { + // accent + // return ['{'+getTeXsymbol(symbol)+'{'+result[0]+'}}',result[1]]; + return [`${getTeXsymbol(symbol)}{${result[0]}}`, result[1]]; + } else { + // font change command + return [`{${getTeXsymbol(symbol)}{${result[0]}}}`, result[1]]; + } + case tokens.BINARY: + str = removeCharsAndBlanks(str, symbol.input.length); + result = parseSexpr(str); + if (result[0] === null) { + return [`{${getTeXsymbol(symbol)}}`, str]; + } + result[0] = removeBrackets(result[0]); + var result2 = parseSexpr(result[1]); + if (result2[0] === null) { + return [`{${getTeXsymbol(symbol)}}`, str]; + } + result2[0] = removeBrackets(result2[0]); + if (symbol.input === 'color') { + newFrag = `{\\color{${result[0].replace(/[{}]/g, '')}}${result2[0]}}`; + } else if (symbol.input === 'root') { + newFrag = `{\\sqrt[${result[0]}]{${result2[0]}}}`; + } else { + newFrag = `{${getTeXsymbol(symbol)}{${result[0]}}{${result2[0]}}}`; + } + return [newFrag, result2[1]]; + case tokens.INFIX: + str = removeCharsAndBlanks(str, symbol.input.length); + return [symbol.output, str]; + case tokens.SPACE: + str = removeCharsAndBlanks(str, symbol.input.length); + return [`{\\quad\\text{${symbol.input}}\\quad}`, str]; + case tokens.LEFTRIGHT: + // if (rightvert) return [null,str]; else rightvert = true; + nestingDepth++; + str = removeCharsAndBlanks(str, symbol.input.length); + result = parseExpr(str, false); + nestingDepth--; + var st = ''; + st = result[0].charAt(result[0].length - 1); + // alert(result[0].lastChild+'***'+st); + if (st === '|') { + // its an absolute value subterm + node = `{\\left|${result[0]}}`; + return [node, result[1]]; + } else { + // the '|' is a \mid + node = '{\\mid}'; + return [node, str]; + } + + default: + // alert('default'); + str = removeCharsAndBlanks(str, symbol.input.length); + return [`{${getTeXsymbol(symbol)}}`, str]; + } +} + +function parseIexpr(str) { + let sym2; + let node; + let result; + str = removeCharsAndBlanks(str, 0); + const sym1 = getSymbol(str); + result = parseSexpr(str); + node = result[0]; + str = result[1]; + const symbol = getSymbol(str); + if (symbol.ttype === tokens.INFIX && symbol.input !== '/') { + str = removeCharsAndBlanks(str, symbol.input.length); + // if (symbol.input === '/') result = parseIexpr(str); else + result = parseSexpr(str); + // show box in place of missing argument + if (result[0] === null) { + result[0] = '{}'; + } else { + result[0] = removeBrackets(result[0]); + } + str = result[1]; + // if (symbol.input === '/') removeBrackets(node); + if (symbol.input === '_') { + sym2 = getSymbol(str); + if (sym2.input === '^') { + str = removeCharsAndBlanks(str, sym2.input.length); + const res2 = parseSexpr(str); + res2[0] = removeBrackets(res2[0]); + str = res2[1]; + node = `{${node}`; + node += `_{${result[0]}}`; + node += `^{${res2[0]}}`; + node += '}'; + } else { + node += `_{${result[0]}}`; + } + } else { + // must be ^ + // node = '{'+node+'}^{'+result[0]+'}'; + node = `${node}^{${result[0]}}`; + } + if (typeof sym1.func !== 'undefined' && sym1.func) { + sym2 = getSymbol(str); + if (sym2.ttype !== tokens.INFIX && sym2.ttype !== tokens.RIGHTBRACKET) { + result = parseIexpr(str); + node = `{${node}${result[0]}}`; + str = result[1]; + } + } + } + + return [node, str]; +} + +function parseExpr(str, rightbracket) { + let symbol; + let node; + let result; + let i; + // const nodeList = []; + let newFrag = ''; + let addedright = false; + do { + str = removeCharsAndBlanks(str, 0); + result = parseIexpr(str); + node = result[0]; + str = result[1]; + symbol = getSymbol(str); + if (symbol.ttype === tokens.INFIX && symbol.input === '/') { + str = removeCharsAndBlanks(str, symbol.input.length); + result = parseIexpr(str); + + // show box in place of missing argument + if (result[0] === null) { + result[0] = '{}'; + } else { + result[0] = removeBrackets(result[0]); + } + str = result[1]; + node = removeBrackets(node); + node = `${'\\frac' + '{'}${node}}`; + node += `{${result[0]}}`; + newFrag += node; + symbol = getSymbol(str); + } else if (node) { + newFrag += node; + } + } while ( + ((symbol.ttype !== tokens.RIGHTBRACKET && + (symbol.ttype !== tokens.LEFTRIGHT || rightbracket)) || + nestingDepth === 0) && + symbol && + symbol.output + ); + if ( + symbol.ttype === tokens.RIGHTBRACKET || + symbol.ttype === tokens.LEFTRIGHT + ) { + // if (nestingDepth > 0) nestingDepth--; + const len = newFrag.length; + if (len > 2 && newFrag.charAt(0) === '{' && newFrag.indexOf(',') > 0) { + // could be matrix (total rewrite from .js) + const right = newFrag.charAt(len - 2); + if (right === ')' || right === ']') { + const left = newFrag.charAt(6); + if ( + (left === '(' && right === ')' && symbol.output !== '}') || + (left === '[' && right === ']') + ) { + let mxout = '\\matrix{'; + const pos = new Array(); // position of commas + pos.push(0); + let matrix = true; + let mxnestingd = 0; + const subpos = []; + subpos[0] = [0]; + let lastsubposstart = 0; + let mxanynestingd = 0; + for (i = 1; i < len - 1; i++) { + if (newFrag.charAt(i) === left) { + mxnestingd++; + } + if (newFrag.charAt(i) === right) { + mxnestingd--; + if ( + mxnestingd === 0 && + newFrag.charAt(i + 2) === ',' && + newFrag.charAt(i + 3) === '{' + ) { + pos.push(i + 2); + lastsubposstart = i + 2; + subpos[lastsubposstart] = [i + 2]; + } + } + if ( + newFrag.charAt(i) === '[' || + newFrag.charAt(i) === '(' || + newFrag.charAt(i) === '{' + ) { + mxanynestingd++; + } + if ( + newFrag.charAt(i) === ']' || + newFrag.charAt(i) === ')' || + newFrag.charAt(i) === '}' + ) { + mxanynestingd--; + } + if (newFrag.charAt(i) === ',' && mxanynestingd === 1) { + subpos[lastsubposstart].push(i); + } + if (mxanynestingd < 0) { + // happens at the end of the row + if (lastsubposstart === i + 1) { + // if at end of row, skip to next row + i++; + } else { + // misformed something - abandon treating as a matrix + matrix = false; + } + } + } + + pos.push(len); + let lastmxsubcnt = -1; + if (mxnestingd === 0 && pos.length > 0 && matrix) { + for (i = 0; i < pos.length - 1; i++) { + if (i > 0) { + mxout += '\\\\'; + } + let subarr; + if (i === 0) { + // subarr = newFrag.substr(pos[i]+7,pos[i+1]-pos[i]-15).split(','); + if (subpos[pos[i]].length === 1) { + subarr = [ + newFrag.substr(pos[i] + 7, pos[i + 1] - pos[i] - 15), + ]; + } else { + const subarr = [ + newFrag.substring(pos[i] + 7, subpos[pos[i]][1]), + ]; + for (let j = 2; j < subpos[pos[i]].length; j++) { + subarr.push( + newFrag.substring( + subpos[pos[i]][j - 1] + 1, + subpos[pos[i]][j], + ), + ); + } + subarr.push( + newFrag.substring( + subpos[pos[i]][subpos[pos[i]].length - 1] + 1, + pos[i + 1] - 8, + ), + ); + } + } else { + // const subarr = newFrag.substr(pos[i]+8,pos[i+1]-pos[i]-16).split(','); + if (subpos[pos[i]].length === 1) { + subarr = [ + newFrag.substr(pos[i] + 8, pos[i + 1] - pos[i] - 16), + ]; + } else { + subarr = [newFrag.substring(pos[i] + 8, subpos[pos[i]][1])]; + for (let j = 2; j < subpos[pos[i]].length; j++) { + subarr.push( + newFrag.substring( + subpos[pos[i]][j - 1] + 1, + subpos[pos[i]][j], + ), + ); + } + subarr.push( + newFrag.substring( + subpos[pos[i]][subpos[pos[i]].length - 1] + 1, + pos[i + 1] - 8, + ), + ); + } + } + if (lastmxsubcnt > 0 && subarr.length !== lastmxsubcnt) { + matrix = false; + } else if (lastmxsubcnt === -1) { + lastmxsubcnt = subarr.length; + } + mxout += subarr.join('&'); + } + } + mxout += '}'; + + if (matrix) { + newFrag = mxout; + } + } + } + } + + str = removeCharsAndBlanks(str, symbol.input.length); + if (typeof symbol.invisible !== 'boolean' || !symbol.invisible) { + node = `\\right${getTeXsymbol(symbol)}`; // AMcreateMmlNode('mo',document.createTextNode(symbol.output)); + newFrag += node; + addedright = true; + } else { + newFrag += '\\right.'; + addedright = true; + } + } + if (nestingDepth > 0 && !addedright) { + newFrag += '\\right.'; // adjust for non-matching left brackets + // todo: adjust for non-matching right brackets + } + + return [newFrag, str]; +} + +/** @param {string} str */ +export default function asciimathToTex(str) { + nestingDepth = 0; + str = str + .replace(/( |\u00a0| )/g, '') + .replace(/>/g, '>') + .replace(/</g, '<'); + if (!str.match(/\S/)) { + return ''; + } + return parseExpr(str.replace(/^\s+/g, ''), false)[0]; +} + +{ + const otherSymbols = symbols + .filter(item => item.tex && item.notexcopy !== true) + .map(item => ({ + input: item.tex, + tag: item.tag, + output: item.output, + ttype: item.ttype, + acc: item.acc || false, + })); + symbols.push(...otherSymbols); + + refreshSymbols(); +} diff --git a/public/react/src/common/showdown-katex/asciimath-to-tex.spec.js b/public/react/src/common/showdown-katex/asciimath-to-tex.spec.js new file mode 100644 index 000000000..ac2f5c9a1 --- /dev/null +++ b/public/react/src/common/showdown-katex/asciimath-to-tex.spec.js @@ -0,0 +1,28 @@ +import test from 'ava'; +import asciimathToTex from './asciimath-to-tex'; + +test('just a number', t => { + t.is(asciimathToTex('5'), '{5}'); +}); + +test('x=5', t => { + t.is(asciimathToTex('x=5'), '{x}={5}'); +}); + +test('x=5+2', t => { + t.is(asciimathToTex('x=5+2'), '{x}={5}+{2}'); +}); + +test('x = (-b+-sqrt(b^2-4ac))/(2a)', t => { + t.is( + asciimathToTex('x = (-b+-sqrt(b^2-4ac))/(2a)'), + '{x}=\\frac{{-{b}\\pm\\sqrt{{{b}^{{2}}-{4}{a}{c}}}}}{{{2}{a}}}', + ); +}); + +test('{x}=(-b+-sqrt(b^2-4ac))/(2a)', t => { + t.is( + asciimathToTex('{x}=(-b+-sqrt(b^2-4ac))/(2a)'), + '{\\left\\lbrace{x}\\right\\rbrace}=\\frac{{-{b}\\pm\\sqrt{{{b}^{{2}}-{4}{a}{c}}}}}{{{2}{a}}}', + ); +}); diff --git a/public/react/src/common/showdown-katex/showdown-katex.js b/public/react/src/common/showdown-katex/showdown-katex.js new file mode 100644 index 000000000..3b2bb7d44 --- /dev/null +++ b/public/react/src/common/showdown-katex/showdown-katex.js @@ -0,0 +1,121 @@ +import katex from 'katex'; +import renderMathInElement from 'katex/dist/contrib/auto-render'; +import showdown from 'showdown'; +import asciimathToTex from './asciimath-to-tex'; + +if (process.env.TARGET === 'cjs') { + const { JSDOM } = require('jsdom'); + const jsdom = new JSDOM(); + global.DOMParser = jsdom.window.DOMParser; + global.document = jsdom.window.document; +} + +/** + * @param {object} opts + * @param {NodeListOf} opts.elements + * @param opts.config + * @param {boolean} opts.isAsciimath + */ +function renderBlockElements({ elements, config, isAsciimath }) { + if (!elements.length) { + return; + } + + elements.forEach(element => { + const input = element.textContent || element.innerText; + const latex = isAsciimath ? asciimathToTex(input) : input; + const html = katex.renderToString(latex, config); + element.parentNode.outerHTML = `${html}`; + }); +} + +/** + * https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + * @param {string} str + * @returns {string} regexp escaped string + */ +function escapeRegExp(str) { + return str.replace(/[-[\]/{}()*+?.\\$^|]/g, '\\$&'); +} + +// katex config +const getConfig = (config = {}) => ({ + displayMode: true, + throwOnError: false, // fail silently + errorColor: '#ff0000', + ...config, + delimiters: [ + { left: '$$', right: '$$', display: false, asciimath: false }, + { left: '~', right: '~', display: false, asciimath: true }, + ].concat(config.delimiters || []), +}); + +const showdownKatex = userConfig => () => { + const parser = new DOMParser(); + const config = getConfig(userConfig); + const asciimathDelimiters = config.delimiters + .filter(item => item.asciimath) + .map(({ left, right }) => { + const l = escapeRegExp(left) + const r = escapeRegExp(right) + const test = new RegExp( + `(?:${l})([^${l}${r}]+)(?:${r})`, + 'g', + ); + const replacer = (match, asciimath) => { + return `${left}${asciimathToTex(asciimath)}${right}`; + }; + return { test, replacer }; + }); + + return [ + { + type: 'output', + filter(html = '') { + const wrapper = parser.parseFromString(html, 'text/html').body; + if (asciimathDelimiters.length) { + wrapper.querySelectorAll(':not(code):not(pre)').forEach(el => { + const textNodes = [...el.childNodes].filter( + node => { + if (node.nodeName === '#text') { + return node.nodeValue.trim() + } + if (node.nodeName === 'CODE') { + return node.innerText.trim() + } + }, + ) + textNodes.forEach(node => { + const newText = asciimathDelimiters.reduce( + (acc, { test, replacer }) => acc.replace(test, replacer), + node.nodeValue || node.innerText + ) + if (node.nodeName === '#text') { + node.nodeValue = newText + } else { + node.innerText = newText; + } + }) + }) + } + + // find the math in code blocks + const latex = wrapper.querySelectorAll('code.latex.language-latex'); + const asciimath = wrapper.querySelectorAll( + 'code.asciimath.language-asciimath', + ); + + renderBlockElements({ elements: latex, config }); + renderBlockElements({ elements: asciimath, config, isAsciimath: true }); + renderMathInElement(wrapper, config); + + return wrapper.innerHTML; + }, + }, + ]; +}; + +// register extension with default config +showdown.extension('showdown-katex', showdownKatex()); + +export default showdownKatex; diff --git a/public/react/src/common/showdown-katex/showdown-katex.spec.js b/public/react/src/common/showdown-katex/showdown-katex.spec.js new file mode 100644 index 000000000..a13a0303c --- /dev/null +++ b/public/react/src/common/showdown-katex/showdown-katex.spec.js @@ -0,0 +1,21 @@ +import test from 'ava'; +import showdown from 'showdown'; +import katex from '../lib/showdown-katex'; + +const input = '# hello, markdown!'; +const output = '

hello, markdown!

'; + +test('string extension', t => { + const converter = new showdown.Converter({ + extensions: ['showdown-katex'], + }); + t.is(converter.makeHtml(input), output); +}); + +test('function extension', t => { + const converter = new showdown.Converter({ + extensions: [katex()], + }); + + t.is(converter.makeHtml(input), output); +}); diff --git a/public/react/src/modules/courses/graduation/tasks/Graduationtaskitem.js b/public/react/src/modules/courses/graduation/tasks/Graduationtaskitem.js index f6af7e0b9..f85c0eee1 100644 --- a/public/react/src/modules/courses/graduation/tasks/Graduationtaskitem.js +++ b/public/react/src/modules/courses/graduation/tasks/Graduationtaskitem.js @@ -17,8 +17,6 @@ export default ({ item, onDelete, isChildCommentPagination, loadMoreChildComment loadMoreChildComments(item) } - console.log(md, '-----------') - return (