mathlive
Version:
Render and edit beautifully typeset math
431 lines (371 loc) • 14.2 kB
JavaScript
/**
* This module contains utilities to debug mathlive internal data structures.
*
* It is also used by the automated test suite.
*
* @module addons/debug
* @private
*/
import { toASCIIMath } from '../editor/outputASCIIMath.js';
import Lexer from '../core/lexer.js';
import ParserModule from '../core/parser.js';
import { parseMathString } from '../editor/editor-editableMathlist.js';
export function latexToAsciiMath(latex, mode) {
mode = mode || 'math';
const mathlist = ParserModule.parseTokens(
Lexer.tokenize(latex), mode, null, null);
return toASCIIMath(mathlist);
}
export function asciiMathToLatex(ascii) {
return parseMathString(ascii, {format: 'ASCIIMath'});
}
/**
*
* @param {object[]} spans
* @param {string|number|number[]} symbol specify which span to consider.
* If a string, a span whose body match the string
* If a number, the nth span in the list
* If an array, each element in the array indicate the nth child to traverse
* @private
*/
function getSymbol(spans, symbol) {
if (!spans) return null;
let childSymbol = null;
if (Array.isArray(symbol)) {
childSymbol = symbol.slice(); // Clone the array
symbol = childSymbol.shift(); // Get the first element and remove it from the array
}
let result = null;
if (typeof symbol === 'number' && symbol < spans.length) {
if (childSymbol && childSymbol.length > 0) {
return getSymbol(spans[symbol].children, childSymbol);
}
return spans[symbol];
} else if (typeof symbol === 'string') {
for (let i = 0; i < spans.length; i++) {
// Does this span match the symbol we're looking for?
if (spans[i].body === symbol) {
if (childSymbol && childSymbol.length > 0) {
return getSymbol(spans[i].children, childSymbol);
}
return spans[i];
}
// If not, try its children
result = getSymbol(spans[i].children, symbol);
if (result) return result;
}
return result;
}
return null;
}
function getProp(spans, symbol, prop) {
const s = getSymbol(spans, symbol);
if (s) return s[prop];
return null;
}
/**
* Return the type ('mbin', etc...) of a span
* @param {Span[]} spans
* @param {string} symbol
* @return {string}
* @private
*/
function getType(spans, symbol) {
const s = getSymbol(spans, symbol);
if (s) return s.type;
return null;
}
/**
* Return the tag ('span', 'var', etc...) of a span
* @param {Span[]} spans
* @param {string} symbol
* @return {string}
* @private
*/
function getTag(spans, symbol) {
const s = getSymbol(spans, symbol);
if (s) return s.tag;
return null;
}
function getStyle(spans, symbol, prop) {
const s = getSymbol(spans, symbol);
if (s && s.style) return s.style[prop];
return null;
}
function getClasses(spans, symbol) {
const s = getSymbol(spans, symbol);
if (s) return s.classes || '';
return null;
}
function hasClass(spans, symbol, cls) {
let classes = getClasses(spans, symbol);
if (!classes) return false;
classes = classes.split(' ');
for (let j = 0; j < classes.length; j++) {
if (classes[j] === cls) return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
function spanToString(span, indent) {
indent = indent || '';
let result = '';
if (Array.isArray(span)) {
if (span.length === 0) {
result += '[]\n';
} else {
result += '[\n';
for (let i = 0; i < span.length; i++) {
result += spanToString(span[i], '\t' + indent + i + ',');
result += i < span.length - 1 ? ',\n' : '\n';
}
result += indent + ']\n';
}
} else {
result = indent + '{\n';
if (span.type) {
result += indent + 'type:"' + span.type + '",\n';
}
if (span.body && span.body.length > 0) {
result += indent + 'body:"' + span.body + '",\n';
}
if (span.classes && span.classes.length > 0) {
result += indent + 'classes:"' + span.classes + '",\n';
}
if (span.style) {
for (const s in span.style) {
if (Object.prototype.hasOwnProperty.call(span.style, s)) {
result += indent + s + ':"';
result += span.style[s] + '",\n';
}
}
}
if (span.children && span.children.length > 0) {
result += indent + 'children:' + spanToString(span.children, indent);
}
result += indent + '}';
}
return result;
}
function mathlistPropToString(mathlist, prop, indent) {
const value = mathlist[prop];
if (typeof value === 'string') {
return indent + prop + ':"' + value + '",\n';
} else if (typeof value === 'boolean') {
return indent + prop + ':' + value + ',\n';
} else if (typeof value === 'number') {
return indent + prop + ':' + value + ',\n';
} else if (Array.isArray(value)) {
return indent + prop + ':' + mathlistToString(value, indent + '\t') + ',\n';
}
return '';
}
function mathlistToString(mathlist, indent) {
if (!mathlist) return '';
indent = indent || '';
let result = '';
if (Array.isArray(mathlist)) {
if (mathlist.length === 0) return '';
result += '[\n';
for (let i = 0; i < mathlist.length; i++) {
result += mathlistToString(mathlist[i], indent + '\t') + ',\n';
}
result += indent + ']\n';
} else {
result = indent + '{\n';
result += mathlistPropToString(mathlist, 'type', indent);
result += mathlistPropToString(mathlist, 'value', indent);
result += mathlistPropToString(mathlist, 'fontFamily', indent);
// Type 'genfrac'
result += mathlistPropToString(mathlist, 'hasBarLine', indent);
result += mathlistPropToString(mathlist, 'leftDelim', indent);
result += mathlistPropToString(mathlist, 'rightDelim', indent);
result += mathlistPropToString(mathlist, 'numer', indent);
result += mathlistPropToString(mathlist, 'denom', indent);
// Type...?
result += mathlistPropToString(mathlist, 'limits', indent);
result += mathlistPropToString(mathlist, 'symbol', indent);
// Type 'color'
result += mathlistPropToString(mathlist, 'framecolor', indent);
// Type 'line'
result += mathlistPropToString(mathlist, 'position', indent);
// Type 'mathstyle'
result += mathlistPropToString(mathlist, 'mathstyle', indent);
// Common
result += mathlistPropToString(mathlist, 'superscript', indent);
result += mathlistPropToString(mathlist, 'subscript', indent);
result += mathlistPropToString(mathlist, 'body', indent);
result += mathlistPropToString(mathlist, 'array', indent);
result += indent + '}';
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
function spanToMarkup(span, indent) {
indent = indent || '';
// if (indent.length === 0) {
// result += '<table>';
// }
let result = '';
if (Array.isArray(span)) {
for (let i = 0; i < span.length; i++) {
result += spanToMarkup(span[i], indent);
}
} else if (span && span.tag !== 'table') {
result = '<br>' + indent;
if (span.classes.includes('fontsize-ensurer')) {
result += 'FONTSIZE-ENSURER';
} else {
if (span.type) {
result += '<span class="type">' + span.type + '</span>';
}
if (span.body && span.body.length > 0) {
result += '<span class="value">' + span.body + '</span>';
}
if (span.classes && span.classes.length > 0) {
result += ' <span class="classes">' + span.classes + '</span>';
}
if (span.isTight) {
result += ' <span class="stylevalue"> tight </span>';
}
if (span.caret) {
result += ' <span class="stylevalue"> caret </span>';
}
if (span.style) {
for (const s in span.style) {
if (Object.prototype.hasOwnProperty.call(span.style, s)) {
result += ' <span class="styleprop">' + s + ':</span>';
result += '<span class="stylevalue"> ' + span.style[s] + '</span>; ';
}
}
}
if (span.children) {
result += spanToMarkup(span.children, indent + '▷');
}
}
} else if (span) {
result += '<br>' + indent + 'table ' + span.array[0].length + '×' + span.array.length;
for (let i = 0; i < span.array.length; i++) {
for (let j = 0; j < span.array[i].length; j++) {
result += '<br>' + indent + '[' + (i + 1) + ', ' + (j + 1) + '] ';
result += spanToMarkup(span.array[i][j], '');
}
}
}
return result;
}
function mathListColorToMarkup(mathlist, propname) {
let result = '';
if (mathlist[propname]) {
result += '<span class="styleprop">' + propname + '=</span>';
result += '<span style="font-size:2em;vertical-align:middle;color:' + mathlist[propname] + '">■</span>';
result += '<span class="stylevalue">';
result += mathlist[propname];
result += '</span>';
}
return result;
}
function mathListPropToMarkup(mathlist, propname) {
let result = '';
if (mathlist[propname]) {
result += '<span class="styleprop">' + propname + '=</span>';
result += '<span class="stylevalue">';
result += mathlist[propname]
result += '</span>" ';
}
return result;
}
function mathlistToMarkup(mathlist, indent) {
if (!mathlist) return '';
indent = indent || '';
let result = '';
if (Array.isArray(mathlist)) {
for (let i = 0; i < mathlist.length; i++) {
result += mathlistToMarkup(mathlist[i], i + '.' + indent);
}
} else {
result = '<br>' + indent;
if (mathlist.type) {
result += '<span class="type';
result += mathlist.isSelected ? ' selected' : '';
result += mathlist.caret ? ' caret' : '';
result += '">' + mathlist.type +
(mathlist.caret ? ' caret ' : '') + '</span>';
}
if (typeof mathlist.body === 'string' && mathlist.body.length > 0) {
result += ' <span class="value">';
result += mathlist.body;
if (mathlist.body.charCodeAt(0) < 32
|| mathlist.body.charCodeAt(0) > 127) {
result += ' U+' + ('000000' +
mathlist.body.charCodeAt(0).toString(16)).substr(-6);
}
result += '</span> ';
}
if (mathlist.fontFamily === 'mathrm') {
result += '<span style="opacity:.2">';
result += mathListPropToMarkup(mathlist, 'fontFamily');
result += '</span>';
} else {
result += mathListPropToMarkup(mathlist, 'fontFamily');
}
// Type 'genfrac'
result += mathListPropToMarkup(mathlist, 'hasBarLine');
result += mathListPropToMarkup(mathlist, 'leftDelim');
result += mathListPropToMarkup(mathlist, 'rightDelim');
result += mathListPropToMarkup(mathlist, 'continuousFraction');
// Type...?
result += mathListPropToMarkup(mathlist, 'limits');
result += mathListPropToMarkup(mathlist, 'symbol');
// Type 'color'
result += mathListColorToMarkup(mathlist, 'framecolor');
// Type 'mathstyle'
result += mathListPropToMarkup(mathlist, 'mathstyle');
// Type 'sizeddelim'
result += mathListPropToMarkup(mathlist, 'size');
result += mathListPropToMarkup(mathlist, 'cls');
result += mathListPropToMarkup(mathlist, 'delim');
// Type 'rule'
result += mathListPropToMarkup(mathlist, 'shift');
result += mathListPropToMarkup(mathlist, 'width');
result += mathListPropToMarkup(mathlist, 'height');
// Type 'line'
result += mathListPropToMarkup(mathlist, 'position');
// Type 'overunder'
result += mathlistToMarkup(mathlist.overscript, indent + '↑');
result += mathlistToMarkup(mathlist.underscript, indent + '↓');
result += mathlistToMarkup(mathlist.superscript, indent + '↑');
result += mathlistToMarkup(mathlist.subscript, indent + '↓');
result += mathlistToMarkup(mathlist.body, indent + '▶');
result += mathlistToMarkup(mathlist.numer, indent + '\u25B2');
result += mathlistToMarkup(mathlist.denom, indent + '\u25Bc');
if (mathlist.array) {
for (let i = 0; i < mathlist.array.length; i++) {
result += '<br>' + indent + '\u2317 row ' + (i + 1) + '/' + mathlist.array.length;
for (let j = 0; j < mathlist.array[i].length; j++) {
result += mathlistToMarkup(mathlist.array[i][j], indent + '\u2317\u232A');
}
}
}
}
return result;
}
// Export the public interface for this module
export default {
mathlistToMarkup,
spanToMarkup,
mathlistToString,
spanToString,
hasClass,
getClasses,
getProp,
getStyle,
getType,
getTag,
latexToAsciiMath,
asciiMathToLatex,
}