/** This is a formatter for MathML (presentation format only at this time.) It formats expressions into MathML form. You generally want to call: MathML.format[expr, inline=true] Note: Linebreaking is still unimplemented in Firefox and others: https://bugzilla.mozilla.org/show_bug.cgi?id=534962 https://bugzilla.mozilla.org/show_bug.cgi?id=380266 */ class MathML { /** Formats an expression. This creates the top-level tag and then dispatches to formatExpr[eq] to format the body of the document. indent is the indent level (an integer) to begin the indentation. extra is additional MathML markup to add to the tag, e.g. """class="big" """ */ class format[eq, inline=true, indent=0, extra=""] := { spc = indent[indent] return spc + "\n" + formatExpr[eq, indent+1] + spc + "\n" } /** Formats an expression. This dispatches to the appropriate formatter for the expression type. This is called recursively. */ class formatExpr[eq, indent] := { spc = indent[indent] /* First, determine if an expression can be broken into a numerator and denominator. If the denominator is not 1, this means that it can be broken up. This takes care of division, exponentiation by negative exponents, fractions, and more. This formats into a vertically-separated fraction divided by a horizontal line. */ [num, denom] = frac = numeratorDenominator[eq, true, false] if ! isInteger[denom] or denom != 1 { return spc + "\n" + formatExpr[num, indent+1] + formatExpr[denom, indent+1] + spc + "\n" } type = type[eq] if type == "Add" return formatAdd[eq, indent] if type == "Multiply" return formatMultiply[eq, indent] if type == "Power" return formatPower[eq, indent] if type == "FunctionCall" return formatFunctionCall[eq, indent] if type == "Boolean" return formatBoolean[eq, indent] /** Handle other operators. This should probably be extended to all operator types like <=, >=, >, etc. However, some operators have other than 2 children and formatOperator currently only handles two-argument infix operators. */ if isOperator[eq] and getChildCount[eq] == 2 { return formatOperator[eq, indent] } /** Handle unary operators like NOT and factorial */ if isOperator[eq] and getChildCount[eq] == 1 return formatUnaryOperator[eq, indent] if type == "Symbol" return formatSymbol[eq, indent] if isUnit[eq] return formatUnit[eq, indent] // Handle 2-D arrays only. if isArray[eq] { dims = eq.dimensions[] if length[dims] == 2 return format2D[eq, indent] } if isEnumerating[eq] return formatEnumerating[eq, indent] return "Unidentified: type=" + type + ": $eq" } /** Formats an addition expression. It separates fractions into individual terms. */ class formatAdd[eq, indent] := { spc = indent[indent] spc1 = indent[indent+1] size = getChildCount[eq] ret = spc + "\n" for i=0 to size-1 { child = getChild[eq,i] if type[child] == "Multiply" and isNegativeUnit[getChild[child,0]] { ret = ret + spc1 + "-\n" child = -child } else if i > 0 ret = ret + spc1 + "+\n" // Format lower-precedence children in parentheses ep = getOperatorPrecedence[child] if ep != undef and ep < getOperatorPrecedence[eq] sub = parens[child, indent+1] else sub = formatExpr[child, indent+1] ret = ret + sub } return ret + spc + "\n" } /** Formats a multiplication expression. */ class formatMultiply[eq, indent] := { spc = indent[indent] spc1 = indent[indent+1] size = getChildCount[eq] ret = spc + "\n" for i=0 to size-1 { child = getChild[eq,i] if i > 0 ret = ret + spc1 + " \n" // Special case for negation (multiplication by -1) if (i == 0) and size > 1 and isUnit[child] and (child conforms dimensionless) and child == -1 ret = ret + spc1 + "-\n" else { // Format lower-precedence children in parentheses ep = getOperatorPrecedence[child] if ep != undef and ep < getOperatorPrecedence[eq] expF = parens[child, indent+1] else expF = formatExpr[child, indent+1] ret = ret + expF } } return ret + spc + "\n" } /** Formats a power expression with raised exponent. */ class formatPower[eq, indent] := { base = getChild[eq,0] exp = getChild[eq,1] [num, denom] = numeratorDenominator[exp] if isUnit[num] and (num conforms dimensionless) and num == 1 if isUnit[denom] and (denom conforms dimensionless) and denom == 2 return formatSqrt[base, indent] else if isInteger[denom] return formatRoot[base, exp, indent] separate = false // Format lower-precedence children in parentheses bp = getOperatorPrecedence[base] if bp != undef and bp < getOperatorPrecedence[eq] baseF = parens[base, indent+1] else baseF = formatExpr[base, indent+1] // Format lower-precedence children in parentheses ep = getOperatorPrecedence[exp] if ep != undef and ep < getOperatorPrecedence[eq] expF = parens[exp, indent+1] else expF = formatExpr[exp, indent+1] spc = indent[indent] spc1 = indent[indent+1] return spc + "\n" + baseF + expF + spc + "\n" } /** Formats a square root. */ class formatSqrt[eq, indent] := { spc = indent[indent] return spc + "\n" + formatExpr[eq, indent+1] + spc + "\n" } /** Formats a root. Exp should be a rational number with 1 as the numerator. */ class formatRoot[base, exp, indent] := { spc = indent[indent] return spc + "\n" + formatExpr[base, indent+1] + formatUnit[denominator[exp], indent+1] + spc + "\n" } /** Formats a function call in mathematical notation. */ class formatFunctionCall[eq, indent] := { spc = indent[indent] spc1 = indent[indent+1] spc2 = indent[indent+2] spc3 = indent[indent+3] res = spc + "\n" + spc1 + "" + HTMLEncode[getChild[eq, 0]] + "\n" + spc1 + "\n" + spc1 + "\n" + spc2 + "[\n" + spc2 + "\n" for i = 1 to getChildCount[eq]-1 { if i > 1 res = res + spc3 + ",\n" res = res + formatExpr[getChild[eq,i], indent+3] } return res + spc2 + "\n" + spc2 + "]\n" + spc1 + "\n" + spc + "\n" } /** Format operator that has 2 children and is infix. */ class formatOperator[eq, indent] := { left = getChild[eq,0] right = getChild[eq,1] // Format lower-precedence children in parentheses lp = getOperatorPrecedence[left] if lp != undef and lp < getOperatorPrecedence[eq] leftF = parens[left, indent+1] else leftF = formatExpr[left, indent+1] rp = getOperatorPrecedence[right] if rp != undef and rp < getOperatorPrecedence[eq] rightF = parens[right, indent+1] else rightF = formatExpr[right, indent+1] op = getOperatorSymbol[eq] if (op =~ %r/(\w)/) { } else trim[op] spc = indent[indent] spc1 = indent[indent+1] return spc + "\n" + leftF + spc1 + "" + HTMLEncode[op] + "\n" + rightF + spc + "\n" } /** Format operator that has 1 child. */ class formatUnaryOperator[eq, indent] := { child = getChild[eq,0] // Format lower-precedence children in parentheses lp = getOperatorPrecedence[child] if lp != undef and lp < getOperatorPrecedence[eq] leftF = parens[child, indent+1] else leftF = formatExpr[child, indent+1] spc = indent[indent] spc1 = indent[indent+1] if type[eq] == "NOT" return spc + "\n" + spc1 + "" + HTMLEncode[trim[getOperatorSymbol[eq]]] + "\n"+ spc1 + leftF + "\n" + spc + "\n" if type[eq] == "Factorial" return spc + "\n" + spc1 + leftF + "\n" + spc1 + "" + HTMLEncode[trim[getOperatorSymbol[eq]]] + "\n"+ spc + "\n" } /** Put parentheses around the specified expression. */ class parens[eq, indent] := { spc = indent[indent] spc1 = indent[indent+1] return spc + "\n" + spc1 +"(\n" + formatExpr[eq, indent+1] + spc1 +")\n" + spc + "\n" } /** Formats a Unit. */ class formatUnit[eq, indent] := { spc = indent[indent] spc1 = indent[indent+1] // Is a dimensionless real number? if eq conforms dimensionless { if isReal[eq] { if eq >= 0 // Positive number return spc + "" + HTMLEncode[eq] + "\n" else { // Negative number, format as operator plus mn. Ugh. return spc + "\n" + spc1 + "-\n" + formatExpr[-eq, indent+1] + spc + "\n" } } if isComplex[eq] { im = Im[eq] re = Re[eq] hasReal = (re != 0) return spc + "\n" + (hasReal ? spc1 + "(\n" : "") + (hasReal ? formatExpr[Re[eq], indent+1] + spc1 : "") + (im > 0 ? (hasReal ? "+\n" : "") : ("-")) + (im == 1 ? "" : (im >= 0 ? formatExpr[im, indent+1] : formatExpr[-im, indent+1])) + spc1 + " \n" + spc1 + "\n" + //TODO: getImaginarySymbol[]? (hasReal ? spc1 + ")\n" : "") + spc + "\n" } } // TODO: Format dimensions and complex numbers and rational numbers // (Rational numbers should get handled by numeratorDenominator case // in formatExpr) dimensions = dimensionsToArray[eq] size = length[dimensions] ret = spc + "\n" + formatExpr[getScale[eq], indent+1] + spc1 + " \n" spc2 = indent[indent+2] for i=0 to size-1 { [symbol, exp] = dimensions@i if i > 0 ret = ret + spc1 + "· \n" if (exp != 1) ret = ret + spc1 + "\n" + spc2 + "" + HTMLEncode[symbol] + "\n"+ formatUnit[exp, indent+2] + spc1 + "\n" else ret = ret + spc1 + "" + HTMLEncode[symbol] + "\n" } ret = ret + spc + "\n" return ret } class formatSymbol[eq, indent] := { spc = indent[indent] return spc + "" + HTMLEncode[inputFormUnicode[eq]] + "\n" } class formatBoolean[eq, indent] := { spc = indent[indent] return spc + "" + HTMLEncode[inputFormUnicode[eq]] + "\n" } /** Formats an enumerating expression. It separates arrays or other enumerating expressions into individual terms. */ class formatEnumerating[eq, indent] := { spc = indent[indent] spc1 = indent[indent+1] ret = spc + "\n" ret = ret + spc1 + """[\n""" array = toArray[eq] size = length[array] for i=0 to size-1 { expr = array@i ret = ret + formatExpr[expr, indent+2] if i < size-1 ret = ret + spc1 + """,""" } ret = ret + spc1 + """]\n""" ret = ret + spc + "\n" return ret } /** Formats a 2D array as a matrix. */ class format2D[eq, indent] := { spc = indent[indent] spc1 = indent[indent+1] spc2 = indent[indent+2] spc3 = indent[indent+3] [rows,cols] = eq.dimensions[] ret = spc + "\n" ret = ret + spc1 + """[\n""" ret = ret + spc1 + "\n" for r = 0 to rows-1 { ret = ret + spc2 + "\n" for c = 0 to cols-1 { ret = ret + spc3 + "\n" ret = ret + formatExpr[eq@r@c, indent+4] ret = ret + spc3 + "\n" } ret = ret + spc2 + "\n" } ret = ret + spc1 + "\n" ret = ret + spc1 + """]\n""" ret = ret + spc + "\n" return ret } /** Creates an indent with levels spaces. */ class indent[levels] := { repeat[" ", levels] } /* This encodes strings safely for HTML. */ class HTMLEncode[line] := { line = toString[line] line =~ %s/&/&/g; line =~ %s//>/g; return line } /** Test formatting of an expression by rendering it to a file and displays it in a browser. */ class test[eq, inline=true, indent=0, extra=""] := { f = "MathMLTest.html" w = new Writer[f] raw = HTMLEncode[inputFormUnicode[eq]] w.println[""" MathML Test

Raw expression: $raw

"""] w.print[format[eq, inline, 3, extra]] w.println["""

\n \n"""] w.close[] browse[f] } } "MathML.frink included correctly!"