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.
*/

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.
   */

   class format[eq, inline=true, indent=0] :=
   {
      spc = indent[indent]
      return spc + "<math 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]

      /** 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
      {
//         op = getOperatorSymbol[eq]
//         if op == " === " or op == " = " or op == " -> "
         return formatOperator[eq, indent]
      }

      if type == "Symbol"
         return formatSymbol[eq, indent]

      if isUnit[eq]
         return formatUnit[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"
         
         // 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 num == 1
         if 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]
      
      spc = indent[indent]
      spc1 = indent[indent+1]
      return spc + "<mrow>\n" +
            leftF +
            spc1 + "<mo>" + HTMLEncode[trim[getOperatorSymbol[eq]]] + "</mo>\n" +
            rightF  +
            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]
            return spc + "<mrow>\n" +
                    spc1 +"<mo>(</mo>\n" +
                    formatExpr[Re[eq], indent+1] +
                    spc1 + (im > 0 ? ("<mo>+</mo>\n") : ("<mo>-</mo>")) +
                    (im >= 0 ? formatExpr[im, indent+1] : formatExpr[-im, indent+1]) +
                    spc1 + "<mo> &#x2062;<!--INVISIBLE TIMES--> </mo>\n" +
                    spc1 + "<mi> &ImaginaryI; </mi>\n" +  //TODO: getImaginarySymbol[]?
                    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"
   }
   
   /** 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] :=
   {
      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]]
      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 19581 days, 15 hours, 40 minutes ago.