MathML.frink

Download or view MathML.frink in plain text format


/** 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 <math> 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 <math> tag, e.g.
       """class="big" """
   */

   class format[eq, inline=true, indent=0, extra=""] :=
   {
      spc = indent[indent]
      return spc + "<math $extra display=\"" + (inline?"inline":"block") + "\">\n" +
                     formatExpr[eq, indent+1] +
             spc + "</math>\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 + "<mfrac>\n" +
                        formatExpr[num, indent+1] +
                        formatExpr[denom, indent+1] +
                spc + "</mfrac>\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 "<mtext>Unidentified: type=" + type + ": $eq</mtext>"
   }

   /** 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 + "<mrow>\n"
      for i=0 to size-1
      {
         child = getChild[eq,i]
         if type[child] == "Multiply" and isNegativeUnit[getChild[child,0]]
         {
            ret = ret + spc1 + "<mo>-</mo>\n"
            child = -child
         } else
         if i > 0
            ret = ret + spc1 + "<mo>+</mo>\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 + "</mrow>\n"
   }

   /** Formats a multiplication expression. */
   class formatMultiply[eq, indent] :=
   {
      spc = indent[indent]
      spc1 = indent[indent+1]
      size = getChildCount[eq]
      ret = spc + "<mrow>\n"
      for i=0 to size-1
      {
         child = getChild[eq,i]
         
         if i > 0
            ret = ret + spc1 + "<mo>&#x2062;</mo> <!-- &InvisibleTimes; -->\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 + "<mo>-</mo>\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 + "</mrow>\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 + "<msup>\n" +
              baseF +
              expF  +
             spc + "</msup>\n"
   }

   /** Formats a square root. */
   class formatSqrt[eq, indent] :=
   {
      spc = indent[indent]
      return spc + "<msqrt>\n" +
                     formatExpr[eq, indent+1] +
             spc + "</msqrt>\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 + "<mroot>\n" +
                     formatExpr[base, indent+1] +
                     formatUnit[denominator[exp], indent+1] +
             spc + "</mroot>\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 + "<mrow>\n" +
            spc1 + "<mi>" + HTMLEncode[getChild[eq, 0]] + "</mi>\n" +
            spc1 + "<mo>&#x2061;<!--FUNCTION APPLICATION--></mo>\n" +
            spc1 + "<mrow>\n" +
            spc2 + "<mo>[</mo>\n" +
            spc2 + "<mrow>\n"
      
      for i = 1 to getChildCount[eq]-1
      {
         if i > 1
            res = res + spc3 + "<mo>,</mo>\n"
         
         res = res + formatExpr[getChild[eq,i], indent+3]
      }
      
      return res + spc2 + "</mrow>\n" +
                   spc2 + "<mo>]</mo>\n" +
                   spc1 + "</mrow>\n" +
                   spc + "</mrow>\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 + "<mrow>\n" +
            leftF +
            spc1 + "<mo>" + HTMLEncode[op] + "</mo>\n" +
            rightF  +
            spc + "</mrow>\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 + "<mrow>\n" +
           spc1 + "<mo>" + HTMLEncode[trim[getOperatorSymbol[eq]]] + "</mo>\n"+
           spc1 + leftF + "\n" +
          spc + "</mrow>\n"

      if type[eq] == "Factorial"
      return spc + "<mrow>\n" +
           spc1 + leftF + "\n" +
           spc1 + "<mo>" + HTMLEncode[trim[getOperatorSymbol[eq]]] + "</mo>\n"+
          spc + "</mrow>\n"
   }
   
   /** Put parentheses around the specified expression. */
   class parens[eq, indent] :=
   {
      spc = indent[indent]
      spc1 = indent[indent+1]
      return spc + "<mrow>\n" + 
              spc1 +"<mo>(</mo>\n" +
              formatExpr[eq, indent+1] + 
              spc1 +"<mo>)</mo>\n" +
             spc + "</mrow>\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 + "<mn>" + HTMLEncode[eq] + "</mn>\n"
            else
            {
               // Negative number, format as operator plus mn.  Ugh.
               return spc + "<mrow>\n" +
               spc1 + "<mo>-</mo>\n" +
               formatExpr[-eq, indent+1] +
               spc + "</mrow>\n"
            }
         }

         if isComplex[eq]
         {
            im = Im[eq]
            re = Re[eq]
            hasReal = (re != 0)
            return spc + "<mrow>\n" +
                    (hasReal ? spc1 + "<mo>(</mo>\n" : "") +
                    (hasReal ? formatExpr[Re[eq], indent+1] + spc1 : "") +
                    (im > 0 ? (hasReal ? "<mo>+</mo>\n" : "") : ("<mo>-</mo>")) +
                    (im == 1 ? "" : (im >= 0 ? formatExpr[im, indent+1] : formatExpr[-im, indent+1])) +
                    spc1 + "<mo> &#x2062;<!--INVISIBLE TIMES--> </mo>\n" +
                    spc1 + "<mi> &ImaginaryI; </mi>\n" +  //TODO: getImaginarySymbol[]?
                    (hasReal ? spc1 + "<mo>)</mo>\n" : "") +
                   spc + "</mrow>\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 + "<mrow id=\"adfss\">\n" +
                 formatExpr[getScale[eq], indent+1] +
                  spc1 + "<mo>&#x2062;</mo> <!-- &InvisibleTimes; -->\n"

      spc2 = indent[indent+2]
      for i=0 to size-1
      {
         [symbol, exp] = dimensions@i
         if i > 0
            ret = ret + spc1 + "<mo>&#xB7;</mo> <!-- MIDDLE DOT -->\n"
         
         if (exp != 1)
            ret = ret + spc1 + "<msup>\n" +
                        spc2 + "<mi mathvariant=\"normal\">" + HTMLEncode[symbol] + "</mi>\n"+
                        formatUnit[exp, indent+2] +
                        spc1 + "</msup>\n"
         else
            ret = ret + spc1 + "<mi mathvariant=\"normal\">" + HTMLEncode[symbol] + "</mi>\n"
      }
      ret = ret + spc + "</mrow>\n"
      return ret
   }

   class formatSymbol[eq, indent] :=
   {
      spc = indent[indent]
      return spc + "<mi>" + HTMLEncode[inputFormUnicode[eq]] + "</mi>\n"
   }
   
   class formatBoolean[eq, indent] :=
   {
      spc = indent[indent]
      return spc + "<mi>" + HTMLEncode[inputFormUnicode[eq]] + "</mi>\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 + "<mrow>\n"
      ret = ret + spc1 + """<mo fence="true">[</mo>\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 + """<mo separator="true">,</mo>"""
      }
      ret = ret + spc1 + """<mo fence="true">]</mo>\n"""
      ret = ret + spc + "</mrow>\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 + "<mrow>\n"
      ret = ret + spc1 + """<mo fence="true">[</mo>\n"""
      ret = ret + spc1 + "<mtable>\n"
      for r = 0 to rows-1
      {
         ret = ret + spc2 + "<mtr>\n"
         for c = 0 to cols-1
         {
            ret = ret + spc3 + "<mtd>\n"
            ret = ret + formatExpr[eq@r@c, indent+4]
            ret = ret + spc3 + "</mtd>\n"
         }
         ret = ret + spc2 + "</mtr>\n"
      }
      ret = ret + spc1 + "</mtable>\n"
      ret = ret + spc1 + """<mo fence="true">]</mo>\n"""
      ret = ret + spc + "</mrow>\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/&/&amp;/g;
      line =~ %s/</&lt;/g;
      line =~ %s/>/&gt;/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["""<!doctype html>
<html>
 <head>
  <title>MathML Test</title>
  <meta charset="UTF-8">
 </head>
 <BODY>
  <P>
      Raw expression:  <CODE>$raw</CODE>
  </P>

  <P>"""]

      w.print[format[eq, inline, 3, extra]]
      w.println["""  </P>\n </BODY>\n</HTML>"""]
      w.close[]
      browse[f]
   }
}

"MathML.frink included correctly!"


Download or view MathML.frink in plain text format


This is a program written in the programming language Frink.
For more information, view the Frink Documentation or see More Sample Frink Programs.

Alan Eliasen was born 19966 days, 13 hours, 16 minutes ago.