[prev in list] [next in list] [prev in thread] [next in thread] 

List:       lon-capa-cvs
Subject:    [LON-CAPA-cvs] cvs: loncom /build compressjs.sh /homework response.pm /html/adm/LC_math_editor build
From:       damieng <damieng () source ! lon-capa ! org>
Date:       2014-09-24 18:14:39
Message-ID: cvsdamieng1411582479 () cvsserver
[Download RAW message or body]

This is a MIME encoded message


damieng		Wed Sep 24 18:14:39 2014 EDT

  Added files:                 
    /loncom/build	compressjs.sh 
    /loncom/html/adm/LC_math_editor	build.sh test.html 
    /loncom/html/adm/LC_math_editor/dist	LC_math_editor.min.js 
    /loncom/html/adm/LC_math_editor/src	definitions.js enode.js 
                                       	operator.js parse_exception.js 
                                       	parser.js token.js tokenizer.js 
                                       	ui.js 

  Modified files:              
    /doc/loncapafiles	loncapafiles.lpml 
    /loncom/homework	response.pm 
  Log:
  added new LON-CAPA math editor for mathresponse and formularesponse to replace the DragMath applet
  
["damieng-20140924181439.txt" (text/plain)]

Index: doc/loncapafiles/loncapafiles.lpml
diff -u doc/loncapafiles/loncapafiles.lpml:1.897 \
                doc/loncapafiles/loncapafiles.lpml:1.898
--- doc/loncapafiles/loncapafiles.lpml:1.897	Wed Aug 20 18:02:12 2014
+++ doc/loncapafiles/loncapafiles.lpml	Wed Sep 24 18:14:19 2014
@@ -2,7 +2,7 @@
  "http://lpml.sourceforge.net/DTD/lpml.dtd">
 <!-- loncapafiles.lpml -->
 
-<!-- $Id: loncapafiles.lpml,v 1.897 2014/08/20 18:02:12 raeburn Exp $ -->
+<!-- $Id: loncapafiles.lpml,v 1.898 2014/09/24 18:14:19 damieng Exp $ -->
 
 <!--
 
@@ -613,6 +613,12 @@
 </directory>
 <directory dist='default'>
   <protectionlevel>modest_delete</protectionlevel>
+  <targetdir dist='default'>home/httpd/html/adm/LC_math_editor </targetdir>
+  <categoryname>server readonly</categoryname>
+  <description>LON-CAPA math editor</description>
+</directory>
+<directory dist='default'>
+  <protectionlevel>modest_delete</protectionlevel>
   <targetdir dist='default'>home/httpd/html/ckeditor</targetdir>
   <categoryname>server readonly</categoryname>
   <description>Rich Text Editor</description>
@@ -4650,6 +4656,19 @@
 </file>
 
 <fileglob>
+  <glob>*.js</glob>
+  <sourcedir>loncom/html/adm/dragmath/dist/</sourcedir>
+  <targetdir dist='default'>home/httpd/html/adm/LC_math_editor/</targetdir>
+  <categoryname>interface file</categoryname>
+  <description>
+LON-CAPA math editor
+  </description>
+  <filenames>
+LC_math_editor.min.js
+  </filenames>
+</fileglob>
+
+<fileglob>
   <glob>*.*</glob>
   <sourcedir>loncom/html/adm/jsMath/</sourcedir>
   <targetdir dist='default'>home/httpd/html/adm/jsMath/</targetdir>
Index: loncom/homework/response.pm
diff -u loncom/homework/response.pm:1.236 loncom/homework/response.pm:1.237
--- loncom/homework/response.pm:1.236	Thu Aug 28 14:41:18 2014
+++ loncom/homework/response.pm	Wed Sep 24 18:14:27 2014
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # various response type definitons response definition
 #
-# $Id: response.pm,v 1.236 2014/08/28 14:41:18 raeburn Exp $
+# $Id: response.pm,v 1.237 2014/09/24 18:14:27 damieng Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -578,10 +578,15 @@
 
 sub edit_mathresponse_button {
     my ($id,$field)=@_;
-    my $button=&mt('Edit Answer');
-#    my $helplink=&Apache::loncommon::help_open_topic('Formula_Editor');
-    my $iconpath=$Apache::lonnet::perlvar{'lonIconsURL'};
-    return(<<ENDFORMULABUTTON);
+    my $btype = $env{'browser.type'};
+    my $bversion = $env{'browser.version'};
+    if (($btype eq 'explorer' && $bversion < 9) || ($btype eq 'safari' && $bversion \
< 3) || +        ($btype eq 'mozilla' && $bversion < 3)) {
+      # DragMath applet
+      my $button=&mt('Edit Answer');
+#     my $helplink=&Apache::loncommon::help_open_topic('Formula_Editor');
+      my $iconpath=$Apache::lonnet::perlvar{'lonIconsURL'};
+      return(<<ENDFORMULABUTTON);
 <script type="text/javascript" language="JavaScript">
 function edit_${id}_${field} (textarea) {
     thenumber = textarea;
@@ -591,6 +596,27 @@
 </script>
 <a href="javascript:edit_${id}_${field}('${field}');void(0);"><img class="stift" \
src="$iconpath/stift.gif" alt="$button" title="$button" /></a>  ENDFORMULABUTTON
+      
+    } else {
+      # LON-CAPA math equation editor
+      return(<<EQ_EDITOR_SCRIPT);
+<script type="text/javascript">
+  var field = document.getElementById('${field}');
+  field.className += ' math'; // note the space
+  var LCMATH_started;
+  if (typeof LCMATH_started === 'undefined') {
+    LCMATH_started = true;
+    var script = document.createElement("script");
+    script.type = "text/javascript";
+    script.src = "/adm/LC_math_editor/LC_math_editor.min.js";
+    document.body.appendChild(script);
+    window.addEventListener('load', function(e) {
+        LCMATH.initEditors();
+    }, false);
+  }
+</script>
+EQ_EDITOR_SCRIPT
+    }
 }
 
 sub end_mathresponse {

Index: loncom/build/compressjs.sh
+++ loncom/build/compressjs.sh
#!/bin/bash

# Constants
SERVICE_URL=http://closure-compiler.appspot.com/compile
NEWFILE="c`date +"%d%m%y"`.js"
OPTIONS=""

if [ "$1" = "-strict" ]
then
        OPTIONS="--data language=ECMASCRIPT5_STRICT"
        shift
fi

# Check if files to compile are provided
if [ $# -eq 0 ]
then
	echo 'Nothing to compile. Specify input files as command arguments. E.g.'
	echo './compressjs file1.js file2.js file3.js'
	exit
fi

# Itearate through all files
for f in $*
do
	if [ -r "${f}" ]
	then
		code="${code} --data-urlencode js_code@${f}"
	else
		echo "File ${f} does not exist or is not readable. Skipped."
	fi
done

# Send request
curl \
--url ${SERVICE_URL} \
--header 'Content-type: application/x-www-form-urlencoded' \
${code} \
--data output_format=json \
--data output_info=compiled_code \
--data output_info=statistics \
--data output_info=errors \
--data compilation_level=SIMPLE_OPTIMIZATIONS \
${OPTIONS} |

python -c '
import json, sys
data = json.load(sys.stdin)

if "errors" in data:
	print "### COMPILATION FAILED WITH ERRORS"
	for err in data["errors"]:
		file = sys.argv[int(err["file"].replace("Input_", "")) + 1]
		print "File: %s, %d:%d" % (file, err["lineno"], err["charno"])
		print "Error: %s" % err["error"]
		print "Line: %s" % err["line"]
		
	print "\nBuild failed.\n"
	
else:
	print "### COMPILATION COMPLETED"
	print "Original size: %db, gziped: %db" % (data["statistics"]["originalSize"], \
data["statistics"]["originalGzipSize"])  print "Compressed size: %db, gziped: %db" % \
(data["statistics"]["compressedSize"], data["statistics"]["compressedGzipSize"])  \
print "Compression rate: %.2f" % (float(data["statistics"]["compressedSize"]) / \
int(data["statistics"]["originalSize"]))

	filename = "'${NEWFILE}'"
	f = open(filename, "w")
	f.write(data["compiledCode"])

	print "\nBuild file %s created.\n" % filename
' $@
Index: loncom/html/adm/LC_math_editor/build.sh
+++ loncom/html/adm/LC_math_editor/build.sh
#!/bin/bash

# this builds dist/LC_math_editor.min.js using google closure compiler with \
compressjs. # It must be run in the math_editor directory.

cd "$(dirname "$0")"
mkdir -p dist
tmp=$(mktemp)
cat <<% >$tmp
"use strict";
var LCMATH = function () {
%
cat src/*.js >>$tmp
cat <<% >>$tmp
    return({
        "Definitions": Definitions,
        "ENode": ENode,
        "Operator": Operator,
        "ParseException": ParseException,
        "Parser": Parser,
        "initEditors": initEditors
    });
}();
%

NEWFILE="c`date +"%d%m%y"`.js"
/bin/sh ../../../build/compressjs.sh -strict $tmp
mv "$NEWFILE" ./dist/LC_math_editor.min.js
rm -f $tmp

Index: loncom/html/adm/LC_math_editor/test.html
+++ loncom/html/adm/LC_math_editor/test.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Math editor test</title>
    <style>
        div.eqnbox { margin: 1em }
        textarea.math { font-family: monospace; height: 3em; width: 100%; }
        span.math-error { border: solid 1px red; min-width: 1px; }
    </style>
</head>
<body>
    <p>Strict syntax, symbolic mode:</p>
    <div class="eqnbox">
        <textarea class="math" spellcheck="false" autofocus="autofocus"></textarea>
    </div>
    <p>Strict syntax, unit mode (no variable):</p>
    <div class="eqnbox">
        <textarea class="math" data-unit_mode="true" data-constants="c, pi, e, hbar, \
amu" spellcheck="false" autofocus="autofocus"></textarea>  </div>
    <p>Lax syntax, symbolic mode:</p>
    <div class="eqnbox">
        <textarea class="math" data-implicit_operators="true" spellcheck="false" \
autofocus="autofocus"></textarea>  </div>
    <p>Lax syntax, unit mode:</p>
    <div class="eqnbox">
        <textarea class="math" data-implicit_operators="true" data-unit_mode="true" \
data-constants="c, pi, e, hbar, amu" spellcheck="false" \
autofocus="autofocus"></textarea>  </div>
    <div class="eqnbox">
        Test in a field <input class="math" data-implicit_operators="true" \
spellcheck="false" autofocus="autofocus"></textarea> with text around (Lax syntax, \
symbolic mode)  </div>
    <script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=MML_HTMLorMML"></script>
  <script src="src/definitions.js"></script>
    <script src="src/enode.js"></script>
    <script src="src/operator.js"></script>
    <script src="src/parse_exception.js"></script>
    <script src="src/parser.js"></script>
    <script src="src/token.js"></script>
    <script src="src/tokenizer.js"></script>
    <script src="src/ui.js"></script>
    <script>
        window.addEventListener('load', function(e) {
            initEditors(); // will be LCMATH.init_editors() with the minimized \
version  }, false);
    </script>
</body>
</html>

Index: loncom/html/adm/LC_math_editor/dist/LC_math_editor.min.js
+++ loncom/html/adm/LC_math_editor/dist/LC_math_editor.min.js
'use strict';var LCMATH=function(){function k(){this.operators=[]}function \
e(a,b,c,f){this.type=a;this.op=b;this.value=c;this.children=f}function \
n(a,b,c,f,e,g){this.id=a;this.arity=b;this.lbp=c;this.rbp=f;this.nud=e;this.led=g}function \
p(a,b,c){this.msg=a;this.from=b;this.to=c?c:this.from}function \
r(a,b,c){this.implicit_operators="undefined"==typeof \
a?!1:a;this.unit_mode="undefined"==typeof b?!1:b;this.constants="undefined"==typeof \
c?[]:c;this.defs=new k;this.defs.define();this.operators=this.defs.operators; \
this.oph={};for(a=0;a<this.operators.length;a++)this.oph[this.operators[a].id]=this.operators[a]}function \
s(a,b){this.defs=a;this.text=b}function \
m(a,b,c,f,e){this.type=a;this.from=b;this.to=c;this.value=f;this.op=e}k.ARG_SEPARATOR= \
";";k.DECIMAL_SIGN_1=".";k.DECIMAL_SIGN_2=",";k.prototype.operator=function(a,b,c,f,e,g){this.operators.push(new \
n(a,b,c,f,e,g))};k.prototype.separator=function(a){this.operator(a,n.BINARY,0,0,null,null)};k.prototype.infix=function(a,b,c,f){var \
l;l=n.BINARY;f=f||function(b, d){var f=[d,b.expression(c)];return new \
e(e.OPERATOR,this,a,f)};this.operator(a,l,b,c,null,f)};k.prototype.prefix=function(a,b,c){var \
f;f=n.UNARY;c=c||function(c){c=[c.expression(b)];return new \
e(e.OPERATOR,this,a,c)};this.operator(a,f,0,b,c,null)};k.prototype.suffix=function(a,b,c){var \
f;f=n.UNARY;c=c||function(b,c){return new \
e(e.OPERATOR,this,a,[c])};this.operator(a,f,b,0,null,c)};k.prototype.findOperator=function(a){for(var \
b=0;b<this.operators.length;b++)if(this.operators[b].id==a)return this.operators[b]; \
return null};k.prototype.define=function(){this.suffix("!",160);this.infix("^",140,139);this.infix(".",130,129);this.infix("`",125,125,function(a,b){for(var \
c=a.expression(125);null!=a.current_token&&-1!="*/".indexOf(a.current_token.value);){var \
f=a.tokens[a.token_nr];if(null==f)break;if(f.type!=m.NAME&&"("!=f.value)break;f=a.toke \
ns[a.token_nr+1];if(null!=f&&("("==f.value||f.type==m.NUMBER))break;if(a.unit_mode&&a.tokens[a.token_nr].type==m.NAME){for(var \
f=a.tokens[a.token_nr].value,l=!1,g=0;g<a.constants.length;g++)if(f== \
a.constants[g]){l=!0;break}if(l)break}f=a.current_token;a.advance();c=f.op.led(a,c)}return \
new e(e.OPERATOR,this,"`",[b,c])});this.infix("*",120,120);this.infix("/",120,120);thi \
s.infix("%",120,120);this.infix("+",100,100);this.operator("-",n.BINARY,100,134,function(a){a=[a.expression(134)];return \
new e(e.OPERATOR,this,"-",a)},function(a,b){var c=[b,a.expression(100)];return new \
e(e.OPERATOR,this,"-",c)});this.infix("=",80,80);this.infix("#",80,80);this.infix("<=",80,80);this.infix(">=",80,80);this.infix("<",
 80,80);this.infix(">",80,80);this.separator(")");this.separator(k.ARG_SEPARATOR);this.operator("(",n.BINARY,200,200,function(a){var \
b=a.expression(0);a.advance(")");return \
b},function(a,b){if(b.type!=e.NAME&&b.type!=e.SUBSCRIPT)throw new p("Function name \
expected before a parenthesis.",a.tokens[a.token_nr-1].from);var \
c=[b];if(null==a.current_token||null==a.current_token.op||")"!==a.current_token.op.id) \
for(;;){c.push(a.expression(0));if(null==a.current_token||null==a.current_token.op||a.current_token.op.id!==
 k.ARG_SEPARATOR)break;a.advance(k.ARG_SEPARATOR)}a.advance(")");return new \
e(e.FUNCTION,this,"(",c)});this.separator("]");this.operator("[",n.BINARY,200,70,function(a){var \
b=[];if(null==a.current_token||null==a.current_token.op||"]"!==a.current_token.op.id)f \
or(;;){b.push(a.expression(0));if(null==a.current_token||null==a.current_token.op||a.c \
urrent_token.op.id!==k.ARG_SEPARATOR)break;a.advance(k.ARG_SEPARATOR)}a.advance("]");return \
new e(e.VECTOR,this,null,b)},function(a,b){if(b.type!=e.NAME&&b.type!= \
e.SUBSCRIPT)throw new p("Name expected before a square \
bracket.",a.tokens[a.token_nr-1].from);var \
c=[b];if(null==a.current_token||null==a.current_token.op||"]"!==a.current_token.op.id) \
for(;;){c.push(a.expression(0));if(null==a.current_token||null==a.current_token.op||a. \
current_token.op.id!==k.ARG_SEPARATOR)break;a.advance(k.ARG_SEPARATOR)}a.advance("]");return \
new e(e.SUBSCRIPT,this,"[",c)})};e.UNKNOWN=0;e.NAME=1;e.NUMBER=2;e.OPERATOR=3;e.FUNCTION=4;e.VECTOR=5;e.SUBSCRIPT=6;e.COLORS="#E01010 \
#0010FF #009000 #FF00FF #00B0B0 #F09000 #800080 #F080A0 #6090F0 #902000 #70A050 \
#A07060 #5000FF #E06050 #008080 #808000".split(" "); \
e.prototype.toString=function(){var a="(";switch(this.type){case \
e.UNKNOWN:a+="UNKNOWN";break;case e.NAME:a+="NAME";break;case \
e.NUMBER:a+="NUMBER";break;case e.OPERATOR:a+="OPERATOR";break;case \
e.FUNCTION:a+="FUNCTION";break;case e.VECTOR:a+="VECTOR";break;case \
e.SUBSCRIPT:a+="SUBSCRIPT"}this.op&&(a+=" '"+this.op.id+"'");this.value&&(a+=" \
'"+this.value+"'");if(this.children){for(var a=a+" \
[",b=0;b<this.children.length;b++)a+=this.children[b].toString(),b!=this.children.length-1&&(a+=",");a+="]"}return \
a+ ")"};e.prototype.getColorForIdentifier=function(a,b){var \
c=b[a];c||(c=e.COLORS[Object.keys(b).length%e.COLORS.length],b[a]=c);return \
c};e.prototype.toMathML=function(a){var b,c,f,l,g,d,h;"undefined"==typeof \
a&&(a={hcolors:{},depth:0});b=null!=this.children&&0<this.children.length?this.childre \
n[0]:null;c=null!=this.children&&1<this.children.length?this.children[1]:null;f=null!= \
this.children&&2<this.children.length?this.children[2]:null;l=null!=this.children&&3<this.children.length?this.children[3]:
 null;g=null!=this.children&&4<this.children.length?this.children[4]:null;switch(this.type){case \
e.UNKNOWN:return d=document.createElement("mtext"),d.appendChild(document.createTextNode("???")),d;case \
e.NAME:return 0<=this.value.search(/^[a-zA-Z]+[0-9]+$/)?(d=this.value.search(/[0-9]/), \
f=document.createElement("msub"),f.appendChild(this.mi(this.value.substring(0,d))),f.a \
ppendChild(this.mn(this.value.substring(d))),d=f):d=this.mi(this.value),d.setAttribute("mathcolor",this.getColorForIdentifier(this.value,
 a.hcolors)),d;case e.NUMBER:return-1!=this.value.indexOf("e")||-1!=this.value.indexOf \
("E")?(a=this.value.indexOf("e"),-1==a&&(a=this.value.indexOf("E")),d=document.createE \
lement("mrow"),d.appendChild(this.mn(this.value.substring(0,a))),d.appendChild(this.mo \
("\u22c5")),b=document.createElement("msup"),b.appendChild(this.mn(10)),b.appendChild(this.mn(this.value.substring(a+1))),d.appendChild(b),d):this.mn(this.value);case \
e.OPERATOR:if("/"==this.value)g=document.createElement("mfrac"),g.appendChild(b.toMathML(a)),
 g.appendChild(c.toMathML(a)),d=g;else \
if("^"==this.value)f=b.type==e.FUNCTION?"sqrt"==b.value||"abs"==b.value||"matrix"==b.v \
alue||"diff"==b.value?!1:!0:b.type==e.OPERATOR?!0:!1,d=document.createElement("msup"), \
f?d.appendChild(this.addP(b,a)):d.appendChild(b.toMathML(a)),d.appendChild(c.toMathML(a));else \
if("*"==this.value){d=document.createElement("mrow");b.type!=e.OPERATOR||"+"!=b.value& \
&"-"!=b.value?d.appendChild(b.toMathML(a)):d.appendChild(this.addP(b,a));for(f=c;f.type==e.OPERATOR;)f=f.children[0];
 b.type==e.VECTOR&&c.type==e.VECTOR?d.appendChild(this.mo("*")):f.type==e.NUMBER&&d.ap \
pendChild(this.mo("\u22c5"));c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,a))}else \
if("-"==this.value)d=document.createElement("mrow"),1==this.children.length?(d.appendC \
hild(this.mo("-")),d.appendChild(b.toMathML(a))):(d.appendChild(b.toMathML(a)),d.appen \
dChild(this.mo("-")),c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,
 a)));else if("!"==this.value)d=document.createElement("mrow"),h=this.mo(this.value),b \
.type!=e.OPERATOR||"+"!=b.value&&"-"!=b.value?d.appendChild(b.toMathML(a)):d.appendChild(this.addP(b,a)),d.appendChild(h);else \
if("+"==this.value){d=document.createElement("mrow");h=this.mo(this.value);d.appendChi \
ld(b.toMathML(a));d.appendChild(h);f=!1;for(l=c;l.type==e.OPERATOR;)if("-"==l.value&&1==l.children.length){f=!0;break}else \
if("+"==l.value||"-"==l.value||"*"==l.value)l=l.children[0];else \
break;f?d.appendChild(this.addP(c, \
a)):d.appendChild(c.toMathML(a))}else"."==this.value?(d=document.createElement("mrow") \
,b.type!=e.OPERATOR||"+"!=b.value&&"-"!=b.value?d.appendChild(b.toMathML(a)):d.appendC \
hild(this.addP(b,a)),d.appendChild(this.mo("\u22c5")),c.type!=e.OPERATOR||"+"!=c.value \
&&"-"!=c.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,a))):"`"==this.v \
alue?(d=document.createElement("mrow"),b.type!=e.OPERATOR||"+"!=b.value&&"-"!=b.value? \
d.appendChild(b.toMathML(a)):d.appendChild(this.addP(b,a)),f=document.createElement("mstyle"),
 f.setAttribute("fontstyle","normal"),c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?f \
.appendChild(c.toMathML(a)):f.appendChild(this.addP(c,a)),d.appendChild(f)):(d=documen \
t.createElement("mrow"),h=this.mo(this.value),d.appendChild(b.toMathML(a)),d.appendChild(h),d.appendChild(c.toMathML(a)));return \
d;case e.FUNCTION:if("sqrt"==b.value&&null!=c)d=document.createElement("msqrt"),d.appendChild(c.toMathML(a));else \
if("abs"==b.value&&null!=c)d=document.createElement("mrow"),d.appendChild(this.mo("|")),
 d.appendChild(c.toMathML(a)),d.appendChild(this.mo("|"));else \
if("exp"==b.value&&null!=c)d=document.createElement("msup"),d.appendChild(this.mi("e")),d.appendChild(c.toMathML(a));else \
if("factorial"==b.value)d=document.createElement("mrow"),h=this.mo("!"),c.type!=e.OPER \
ATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,a)),d.appendChild(h);else \
if("diff"==b.value&&null!=this.children&&3==this.children.length)d=document.createElement("mrow"),g=document.createElement("mfrac"),
 g.appendChild(this.mi("d")),h=document.createElement("mrow"),h.appendChild(this.mi("d \
")),h.appendChild(this.mi(f.value)),g.appendChild(h),d.appendChild(g),c.type!=e.OPERAT \
OR||"+"!=c.value&&"-"!=c.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,a));else \
if("diff"==b.value&&null!=this.children&&4==this.children.length)d=document.createElem \
ent("mrow"),g=document.createElement("mfrac"),b=document.createElement("msup"),b.appendChild(this.mi("d")),b.appendChild(l.toMathML(a)),g.appendChild(b),
 h=document.createElement("mrow"),h.appendChild(this.mi("d")),b=document.createElement \
("msup"),b.appendChild(f.toMathML(a)),b.appendChild(l.toMathML(a)),h.appendChild(b),g. \
appendChild(h),d.appendChild(g),c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,a));else \
if("integrate"==b.value&&null!=this.children&&3==this.children.length)d=document.creat \
eElement("mrow"),h=this.mo("\u222b"),h.setAttribute("stretchy","true"),d.appendChild(h),f.type!=e.OPERATOR||
 "+"!=f.value&&"-"!=f.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,a)),d.appendChild(this.mi("d")),d.appendChild(f.toMathML(a));else \
if("integrate"==b.value&&null!=this.children&&5==this.children.length)d=document.creat \
eElement("mrow"),b=document.createElement("msubsup"),h=this.mo("\u222b"),h.setAttribut \
e("stretchy","true"),b.appendChild(h),b.appendChild(l.toMathML(a)),b.appendChild(g.toM \
athML(a)),d.appendChild(b),f.type!=e.OPERATOR||"+"!=f.value&&"-"!=f.value?d.appendChild(c.toMathML(a)):
 d.appendChild(this.addP(c,a)),d.appendChild(this.mi("d")),d.appendChild(f.toMathML(a));else \
if("sum"==b.value&&null!=this.children&&5==this.children.length)d=document.createEleme \
nt("mrow"),b=document.createElement("munderover"),h=this.mo("\u2211"),h.setAttribute(" \
stretchy","true"),b.appendChild(h),h=document.createElement("mrow"),h.appendChild(f.to \
MathML(a)),h.appendChild(this.mo("=")),h.appendChild(l.toMathML(a)),b.appendChild(h),b.appendChild(g.toMathML(a)),d.appendChild(b),f.type!=e.OPERATOR||
 "+"!=f.value&&"-"!=f.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,a));else \
if("product"==b.value&&null!=this.children&&5==this.children.length)d=document.createE \
lement("mrow"),b=document.createElement("munderover"),h=this.mo("\u220f"),h.setAttribu \
te("stretchy","true"),b.appendChild(h),h=document.createElement("mrow"),h.appendChild( \
f.toMathML(a)),h.appendChild(this.mo("=")),h.appendChild(l.toMathML(a)),b.appendChild(h),b.appendChild(g.toMathML(a)),d.appendChild(b),f.type!=e.OPERATOR||
 "+"!=f.value&&"-"!=f.value?d.appendChild(c.toMathML(a)):d.appendChild(this.addP(c,a));else \
if("limit"==b.value)d=document.createElement("mrow"),4>this.children.length?d.appendCh \
ild(this.mo("lim")):(b=document.createElement("munder"),b.appendChild(this.mo("lim")), \
h=document.createElement("mrow"),h.appendChild(f.toMathML(a)),h.appendChild(this.mo("\ \
u2192")),h.appendChild(l.toMathML(a)),null!=g&&("plus"==g.value?h.appendChild(this.mo("+")):"minus"==g.value&&h.appendChild(this.mo("-"))),b.appendChild(h),
 d.appendChild(b)),d.appendChild(c.toMathML(a));else{if("binomial"==b.value){d=documen \
t.createElement("mrow");d.appendChild(this.mo("("));l=document.createElement("mtable") \
;for(c=1;c<this.children.length;c++)b=document.createElement("mtr"),b.appendChild(this.children[c].toMathML(a)),l.appendChild(b);d.appendChild(l)}else \
if("matrix"==b.value){for(c=1;c<this.children.length;c++)if(this.children[c].type!==e.VECTOR)return \
d=document.createElement("mtext"),d.appendChild(document.createTextNode("???")), \
d;d=document.createElement("mrow");d.appendChild(this.mo("("));l=document.createElemen \
t("mtable");for(c=1;c<this.children.length;c++){b=document.createElement("mtr");for(f= \
0;f<this.children[c].children.length;f++)b.appendChild(this.children[c].children[f].toMathML(a));l.appendChild(b)}d.appendChild(l)}else \
for(d=document.createElement("mrow"),d.appendChild(b.toMathML(a)),d.appendChild(this.m \
o("(")),c=1;c<this.children.length;c++)d.appendChild(this.children[c].toMathML(a)),c<this.children.length-1&&
 d.appendChild(this.mo(k.ARG_SEPARATOR));d.appendChild(this.mo(")"))}return d;case \
e.VECTOR:g=!0;for(c=0;c<this.children.length;c++)this.children[c].type!==e.VECTOR&&(g= \
!1);d=document.createElement("mrow");d.appendChild(this.mo("("));l=document.createElem \
ent("mtable");for(c=0;c<this.children.length;c++){b=document.createElement("mtr");if(g \
)for(f=0;f<this.children[c].children.length;f++)b.appendChild(this.children[c].children[f].toMathML(a));else \
b.appendChild(this.children[c].toMathML(a));l.appendChild(b)}d.appendChild(l); \
d.appendChild(this.mo(")"));return d;case \
e.SUBSCRIPT:f=document.createElement("msub");f.appendChild(b.toMathML(a));if(2<this.ch \
ildren.length){d=document.createElement("mrow");for(c=1;c<this.children.length;c++)d.a \
ppendChild(this.children[c].toMathML(a)),c<this.children.length-1&&d.appendChild(this.mo(k.ARG_SEPARATOR));f.appendChild(d)}else \
f.appendChild(c.toMathML(a));return f}};e.prototype.mi=function(a){var \
b=document.createElement("mi");e.symbols[a]&&(a=e.symbols[a]);b.appendChild(document.createTextNode(a));
 return b};e.prototype.mn=function(a){var \
b=document.createElement("mn");b.appendChild(document.createTextNode(a));return \
b};e.prototype.mo=function(a){var \
b=document.createElement("mo");e.symbols[a]&&(a=e.symbols[a]);b.appendChild(document.createTextNode(a));return \
b};e.prototype.addP=function(a,b){var \
c,f;c=document.createElement("mrow");f=this.mo("(");f.setAttribute("mathcolor",e.COLOR \
S[b.depth%e.COLORS.length]);c.appendChild(f);b.depth++;c.appendChild(a.toMathML(b));b.depth--;f=this.mo(")");f.setAttribute("mathcolor",
 e.COLORS[b.depth%e.COLORS.length]);c.appendChild(f);return \
c};e.symbols={alpha:"\u03b1",beta:"\u03b2",gamma:"\u03b3",delta:"\u03b4",epsilon:"\u03 \
b5",zeta:"\u03b6",eta:"\u03b7",theta:"\u03b8",iota:"\u03b9",kappa:"\u03ba",lambda:"\u0 \
3bb",mu:"\u03bc",nu:"\u03bd",xi:"\u03be",omicron:"\u03bf",pi:"\u03c0",rho:"\u03c1",sig \
ma:"\u03c3",tau:"\u03c4",upsilon:"\u03c5",phi:"\u03c6",chi:"\u03c7",psi:"\u03c8",omega \
:"\u03c9",Alpha:"\u0391",Beta:"\u0392",Gamma:"\u0393",Delta:"\u0394",Epsilon:"\u0395",Zeta:"\u0396",
 Eta:"\u0397",Theta:"\u0398",Iota:"\u0399",Kappa:"\u039a",Lambda:"\u039b",Mu:"\u039c", \
Nu:"\u039d",Xi:"\u039e",Omicron:"\u039f",Pi:"\u03a0",Rho:"\u03a1",Sigma:"\u03a3",Tau:" \
\u03a4",Upsilon:"\u03a5",Phi:"\u03a6",Chi:"\u03a7",Psi:"\u03a8",Omega:"\u03a9","#":"\u \
2260",">=":"\u2265","<=":"\u2264",inf:"\u221e",minf:"-\u221e",hbar:"\u210f",G:"\ud835\ \
udca2"};n.UNKNOWN=0;n.UNARY=1;n.BINARY=2;n.TERNARY=3;p.prototype.toString=function(){return \
this.msg+" at "+this.from+" - "+this.to};r.prototype.expression=function(a){var b, \
c=this.current_token;if(null==c)throw new p("Expected something at the \
end",this.tokens[this.tokens.length-1].to+1);this.advance();if(null==c.op)b=new \
e(c.type,null,c.value,null);else{if(null==c.op.nud)throw new p("Unexpected operator \
'"+c.op.id+"'",c.from);b=c.op.nud(this)}for(;null!=this.current_token&&null!=this.curr \
ent_token.op&&a<this.current_token.op.lbp;)c=this.current_token,this.advance(),b=c.op.led(this,b);return \
b};r.prototype.advance=function(a){if(a&&(null==this.current_token||null==this.current_token.op||
 this.current_token.op.id!==a)){if(null==this.current_token)throw new p("Expected \
'"+a+"' at the end",this.tokens[this.tokens.length-1].to+1);throw new p("Expected \
'"+a+"'",this.current_token.from);}this.token_nr>=this.tokens.length?this.current_toke \
n=null:(this.current_token=this.tokens[this.token_nr],this.token_nr+=1)};r.prototype.addHiddenOperators=function(){for(var \
a=this.defs.findOperator("*"),b=this.defs.findOperator("`"),c=!1,f=!1,e=0;e<this.tokens.length-1;e++){var \
g=this.tokens[e],d=this.tokens[e+ \
1];this.unit_mode&&("`"==g.value?c=!0:c&&("^"==g.value?f=!0:f&&g.type==m.NUMBER?f=!1:f \
||g.type!=m.NUMBER?g.type==m.OPERATOR&&-1=="*/^()".indexOf(g.value)?c=!1:g.type==m.NAM \
E&&"("==d.value&&(c=!1):c=!1));if(g.type==m.NAME&&d.type==m.NAME||g.type==m.NUMBER&&d. \
type==m.NAME||g.type==m.NUMBER&&d.type==m.NUMBER||g.type==m.NUMBER&&("("==d.value||"[" \
==d.value)||(")"==g.value||"]"==g.value)&&d.type==m.NAME||(")"==g.value||"]"==g.value) \
                &&d.type==m.NUMBER||(")"==g.value||"]"==g.value)&&"("==d.value){if(g=this.unit_mode&&
                
!c&&(g.type==m.NUMBER||")"==g.value||"]"==g.value)&&(d.type==m.NAME||("("==d.value||"["==d.value)&&this.tokens.length>e+2&&this.tokens[e+2].type==m.NAME)){var \
h,k;d.type==m.NAME?(h=d,k=e+1):(k=e+2,h=this.tokens[k]);for(var \
q=0;q<this.constants.length;q++)if(h.value==this.constants[q]){g=!1;break}if(this.tokens.length>k+1&&"("==this.tokens[k+1].value)for(k="pow \
sqrt abs exp factorial diff integrate sum product limit binomial matrix ln log log10 \
mod signum ceiling floor sin cos tan asin acos atan atan2 sinh cosh tanh asinh acosh \
atanh".split(" "), q=0;q<k.length;q++)if(h.value==k[q]){g=!1;break}}g?(d=new \
m(m.OPERATOR,d.from,d.from,b.id,b),c=!0):d=new \
m(m.OPERATOR,d.from,d.from,a.id,a);this.tokens.splice(e+1,0,d)}}};r.prototype.parse=function(a){this.tokens=(new \
s(this.defs,a)).tokenize();if(0==this.tokens.length)return \
null;this.implicit_operators&&this.addHiddenOperators();this.token_nr=0;this.current_t \
oken=this.tokens[this.token_nr];this.advance();a=this.expression(0);if(null!=this.current_token)throw \
new p("Expected the end",this.current_token.from); return \
a};s.prototype.tokenize=function(){var \
a,b,c,f,e;b=0;a=this.text.charAt(b);e=[];a:for(;a;)if(f=b," \
">=a)b++,a=this.text.charAt(b);else{if("0"<=a&&"9">=a||(a===k.DECIMAL_SIGN_1||a===k.DE \
CIMAL_SIGN_2)&&"0"<=this.text.charAt(b+1)&&"9">=this.text.charAt(b+1)){c="";if(a!==k.D \
ECIMAL_SIGN_1&&a!==k.DECIMAL_SIGN_2)for(b++,c+=a;;){a=this.text.charAt(b);if("0">a||"9 \
"<a)break;b++;c+=a}if(a===k.DECIMAL_SIGN_1||a===k.DECIMAL_SIGN_2)for(b++,c+=a;;){a=this.text.charAt(b);if("0">a||"9"<a)break;b+=1;c+=a}if("e"===
 a||"E"===a){b++;c+=a;a=this.text.charAt(b);if("-"===a||"+"===a)b++,c+=a,a=this.text.charAt(b);if("0">a||"9"<a)throw \
new p("syntax error in number exponent",f,b);do \
b++,c+=a,a=this.text.charAt(b);while("0"<=a&&"9">=a)}var \
g=+c.replace(k.DECIMAL_SIGN_1,".").replace(k.DECIMAL_SIGN_2,".");if(isFinite(g)){e.push(new \
m(m.NUMBER,f,b-1,c,null));continue}else throw new p("syntax error in \
number",f,b);}for(c=0;c<this.defs.operators.length;c++)if(g=this.defs.operators[c],this.text.substring(b,b+g.id.length)===
 g.id){b+=g.id.length;a=this.text.charAt(b);e.push(new \
m(m.OPERATOR,f,b-1,g.id,g));continue \
a}if("a"<=a&&"z">=a||"A"<=a&&"Z">=a){c=a;for(b++;;)if(a=this.text.charAt(b),"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"0"<=a&&"9">=a||"_"===a)c+=a,b++;else \
break;e.push(new m(m.NAME,f,b-1,c,null))}else throw new p("unrecognized \
operator",f,b);}return e};m.UNKNOWN=0;m.NAME=1;m.NUMBER=2;m.OPERATOR=3;"use \
strict";var t=!1;return{Definitions:k,ENode:e,Operator:n,ParseException:p,Parser:r,initEditors:function(){if(!t){t=!0;
 for(var a=[],b=document.getElementsByClassName("math"),c=0;c<b.length;c++){var \
f=b[c];if("TEXTAREA"==f.nodeName||"INPUT"==f.nodeName){var \
e=document.createElement("span");e.setAttribute("style","display:none");f.nextSibling? \
f.parentNode.insertBefore(e,f.nextSibling):f.parentNode.appendChild(e);f.addEventListener("blur",function(a){return \
function(b){a.setAttribute("style","display:none")}}(e),!1);f.addEventListener("focus",function(a){return \
function(b){a.setAttribute("style","display: inline-block; background-color: \
                #FFFFE0")}}(e),
!1);var g="true"===f.getAttribute("data-implicit_operators"),d="true"===f.getAttribute \
("data-unit_mode"),h=f.getAttribute("data-constants");h&&(h=h.split(/[\s,]+/));a[c]={ta:f,output_node:e,oldtxt:"",parser:new \
r(g,d,h)};e=function(b){return function(c){var \
d=a[b];document.activeElement==d.ta&&d.output_node.setAttribute("style","display: \
inline-block; background-color: #FFFFE0");var \
e,f,g,h;e=d.ta;c=d.output_node;e=e.value;g="";for(h=e;h!=g;)g=h,h=g.replace(/\[[^\[\]]*\]/g,"");if(h.split("[").length==
 h.split("]").length){for(g="";h!=g;)g=h,h=g.replace(/\([^\(\)]*\)/g,"");h.split("("). \
length==h.split(")").length&&-1!=h.indexOf(k.ARG_SEPARATOR)&&(e="["+e+"]")}if(e!=d.old \
txt){for(d.oldtxt=e;null!=c.firstChild;)c.removeChild(c.firstChild);c.removeAttribute("title");if(""!=e){d=d.parser;try{if(f=d.parse(e),null!=f){var \
m=document.createElement("math");m.setAttribute("display","block");m.appendChild(f.toM \
athML());c.appendChild(m);MathJax.Hub.Queue(["Typeset",MathJax.Hub,c])}}catch(l){f="error: \
"+l, c.setAttribute("title",f),l instanceof \
p?(c.appendChild(document.createTextNode(e.substring(0,l.from))),f=document.createElem \
ent("span"),f.appendChild(document.createTextNode(e.substring(l.from,l.to+1))),f.class \
Name="math-error",c.appendChild(f),l.to<e.length-1&&c.appendChild(document.createTextN \
ode(e.substring(l.to+1)))):(e=document.createTextNode(f),c.appendChild(e))}}}}}(c);""! \
=f.value&&e();f.addEventListener("change",e,!1);f.addEventListener("keyup",e,!1)}}}}}}();
                
Index: loncom/html/adm/LC_math_editor/src/definitions.js
+++ loncom/html/adm/LC_math_editor/src/definitions.js
/*

Copyright (C) 2014  Michigan State University Board of Trustees

The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version.  The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.

As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.

*/

/**
 * Operator definitions (see function define() at the end).
 * @constructor
 */
function Definitions() {
    this.operators = [];  /* Array of Operator */
}

Definitions.ARG_SEPARATOR = ";";
Definitions.DECIMAL_SIGN_1 = ".";
Definitions.DECIMAL_SIGN_2 = ",";

/**
 * Creates a new operator.
 * @param {string} id - Operator id (text used to recognize it)
 * @param {number} arity - Operator.UNARY, BINARY or TERNARY
 * @param {number} lbp - Left binding power
 * @param {number} rbp - Right binding power
 * @param {function} nud - Null denotation function
 * @param {function} led - Left denotation function
 */
Definitions.prototype.operator = function(id, arity, lbp, rbp, nud, led) {
    this.operators.push(new Operator(id, arity, lbp, rbp, nud, led));
};

/**
 * Creates a new separator operator.
 * @param {string} id - Operator id (text used to recognize it)
 */
Definitions.prototype.separator = function(id) {
    this.operator(id, Operator.BINARY, 0, 0, null, null);
};

/**
 * Creates a new infix operator.
 * @param {string} id - Operator id (text used to recognize it)
 * @param {number} lbp - Left binding power
 * @param {number} rbp - Right binding power
 * @param {ledFunction} [led] - Left denotation function
 */
Definitions.prototype.infix = function(id, lbp, rbp, led) {
    var arity, nud;
    arity = Operator.BINARY;
    nud = null;
    led = led || function(p, left) {
        var children = [left, p.expression(rbp)];
        return new ENode(ENode.OPERATOR, this, id, children);
    };
    this.operator(id, arity, lbp, rbp, nud, led);
};

/**
 * Creates a new prefix operator.
 * @param {string} id - Operator id (text used to recognize it)
 * @param {number} rbp - Right binding power
 * @param {nudFunction} [nud] - Null denotation function
 */
Definitions.prototype.prefix = function(id, rbp, nud) {
    var arity, lbp, led;
    arity = Operator.UNARY;
    lbp = 0;
    nud = nud || function(p) {
        var children = [p.expression(rbp)];
        return new ENode(ENode.OPERATOR, this, id, children);
    };
    led = null;
    this.operator(id, arity, lbp, rbp, nud, led);
};

/**
 * Creates a new suffix operator.
 * @param {string} id - Operator id (text used to recognize it)
 * @param {number} lbp - Left binding power
 * @param {ledFunction} [led] - Left denotation function
 */
Definitions.prototype.suffix = function(id, lbp, led) {
    var arity, rbp, nud;
    arity = Operator.UNARY;
    rbp = 0;
    nud = null;
    led = led || function(p, left) {
        var children = [left];
        return new ENode(ENode.OPERATOR, this, id, children);
    };
    this.operator(id, arity, lbp, rbp, nud, led);
};

/**
 * Returns the defined operator with the given id
 * @param {string} id - Operator id (text used to recognize it)
 * @returns {Operator}
 */
Definitions.prototype.findOperator = function(id) {
    for (var i=0; i<this.operators.length; i++) {
        if (this.operators[i].id == id) {
            return(this.operators[i]);
        }
    }
    return null;
}

/**
 * Defines all the operators.
 */
Definitions.prototype.define = function() {
    this.suffix("!", 160);
    this.infix("^", 140, 139);
    this.infix(".", 130, 129);
    this.infix("`", 125, 125, function(p, left) {
        // led (infix operator)
        // this led for units gathers all the units in an ENode
        var right = p.expression(125);
        while (p.current_token != null && "*/".indexOf(p.current_token.value) != -1) \
{  var token2 = p.tokens[p.token_nr];
            if (token2 == null)
                break;
            if (token2.type != Token.NAME && token2.value != "(")
                break;
            var token3 = p.tokens[p.token_nr+1];
            if (token3 != null && (token3.value == "(" || token3.type == \
Token.NUMBER))  break;
            if (p.unit_mode && p.tokens[p.token_nr].type == Token.NAME) {
                var nv = p.tokens[p.token_nr].value;
                var cst = false;
                for (var i=0; i<p.constants.length; i++) {
                    if (nv == p.constants[i]) {
                        cst = true;
                        break;
                    }
                }
                if (cst)
                    break;
            }
            var t = p.current_token;
            p.advance();
            right = t.op.led(p, right);
        }
        var children = [left, right];
        return new ENode(ENode.OPERATOR, this, "`", children);
    });
    this.infix("*", 120, 120);
    this.infix("/", 120, 120);
    this.infix("%", 120, 120);
    this.infix("+", 100, 100);
    this.operator("-", Operator.BINARY, 100, 134, function(p) {
        // nud (prefix operator)
        var children = [p.expression(134)];
        return new ENode(ENode.OPERATOR, this, "-", children);
    }, function(p, left) {
        // led (infix operator)
        var children = [left, p.expression(100)];
        return new ENode(ENode.OPERATOR, this, "-", children);
    });
    this.infix("=", 80, 80);
    this.infix("#", 80, 80);
    this.infix("<=", 80, 80);
    this.infix(">=", 80, 80);
    this.infix("<", 80, 80);
    this.infix(">", 80, 80);
    
    this.separator(")");
    this.separator(Definitions.ARG_SEPARATOR);
    this.operator("(", Operator.BINARY, 200, 200, function(p) {
        // nud (for parenthesis)
        var e = p.expression(0);
        p.advance(")");
        return e;
    }, function(p, left) {
        // led (for functions)
        if (left.type != ENode.NAME && left.type != ENode.SUBSCRIPT)
            throw new ParseException("Function name expected before a parenthesis.", \
p.tokens[p.token_nr - 1].from);  var children = [left];
        if (p.current_token == null || p.current_token.op == null || \
p.current_token.op.id !== ")") {  while (true) {
                children.push(p.expression(0));
                if (p.current_token == null || p.current_token.op == null || \
p.current_token.op.id !== Definitions.ARG_SEPARATOR) {  break;
                }
                p.advance(Definitions.ARG_SEPARATOR);
            }
        }
        p.advance(")");
        return new ENode(ENode.FUNCTION, this, "(", children);
    });
    
    this.separator("]");
    this.operator("[", Operator.BINARY, 200, 70, function(p) {
        // nud (for vectors)
        var children = [];
        if (p.current_token == null || p.current_token.op == null || \
p.current_token.op.id !== "]") {  while (true) {
                children.push(p.expression(0));
                if (p.current_token == null || p.current_token.op == null || \
p.current_token.op.id !== Definitions.ARG_SEPARATOR) {  break;
                }
                p.advance(Definitions.ARG_SEPARATOR);
            }
        }
        p.advance("]");
        return new ENode(ENode.VECTOR, this, null, children);
    }, function(p, left) {
        // led (for subscript)
        if (left.type != ENode.NAME && left.type != ENode.SUBSCRIPT)
            throw new ParseException("Name expected before a square bracket.", \
p.tokens[p.token_nr - 1].from);  var children = [left];
        if (p.current_token == null || p.current_token.op == null || \
p.current_token.op.id !== "]") {  while (true) {
                children.push(p.expression(0));
                if (p.current_token == null || p.current_token.op == null || \
p.current_token.op.id !== Definitions.ARG_SEPARATOR) {  break;
                }
                p.advance(Definitions.ARG_SEPARATOR);
            }
        }
        p.advance("]");
        return new ENode(ENode.SUBSCRIPT, this, "[", children);
    });
};


Index: loncom/html/adm/LC_math_editor/src/enode.js
+++ loncom/html/adm/LC_math_editor/src/enode.js
/*

Copyright (C) 2014  Michigan State University Board of Trustees

The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version.  The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.

As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.

*/

/**
 * Parsed tree node. ENode.toMathML(hcolors) contains the code for the transformation \
                into MathML.
 * @constructor
 * @param {number} type - ENode.UNKNOWN | NAME | NUMBER | OPERATOR | FUNCTION | \
                VECTOR
 * @param {Operator} op - The operator
 * @param {string} value - Node value as a string, null for type VECTOR
 * @param {Array.<ENode>} children - The children nodes, only for types OPERATOR, \
                FUNCTION, VECTOR, SUBSCRIPT
 */
function ENode(type, op, value, children) {
    this.type = type;
    this.op = op;
    this.value = value;
    this.children = children;
}

ENode.UNKNOWN = 0;
ENode.NAME = 1;
ENode.NUMBER = 2;
ENode.OPERATOR = 3;
ENode.FUNCTION = 4;
ENode.VECTOR = 5;
ENode.SUBSCRIPT = 6;
ENode.COLORS = ["#E01010", "#0010FF", "#009000", "#FF00FF", "#00B0B0", "#F09000", 
                "#800080", "#F080A0", "#6090F0", "#902000", "#70A050", "#A07060",
                "#5000FF", "#E06050", "#008080", "#808000"];

/**
 * Returns the node as a string, for debug
 * @returns {string}
 */
ENode.prototype.toString = function() {
    var s = '(';
    switch (this.type) {
        case ENode.UNKNOWN:
            s += 'UNKNOWN';
            break;
        case ENode.NAME:
            s += 'NAME';
            break;
        case ENode.NUMBER:
            s += 'NUMBER';
            break;
        case ENode.OPERATOR:
            s += 'OPERATOR';
            break;
        case ENode.FUNCTION:
            s += 'FUNCTION';
            break;
        case ENode.VECTOR:
            s += 'VECTOR';
            break;
        case ENode.SUBSCRIPT:
            s += 'SUBSCRIPT';
            break;
    }
    if (this.op)
        s += " '" + this.op.id + "'";
    if (this.value)
        s += " '" + this.value + "'";
    if (this.children) {
        s += ' [';
        for (var i = 0; i < this.children.length; i++) {
            s += this.children[i].toString();
            if (i != this.children.length - 1)
                s += ',';
        }
        s += ']';
    }
    s+= ')';
    return s;
};

/**
 * Returns the color for an identifier.
 * @param {string} name
 * @param {Object.<string, string>} hcolors - hash identifier->color
 * @returns {string}
 */
ENode.prototype.getColorForIdentifier = function(name, hcolors) {
    var res = hcolors[name];
    if (!res) {
        res = ENode.COLORS[Object.keys(hcolors).length % ENode.COLORS.length];
        hcolors[name] = res;
    }
    return res;
}

/**
 * Transforms this ENode into a MathML HTML DOM element.
 * @param {Object} [context] - display context (not needed for the root element)
 * @param {Object.<string, string>} context.hcolors - hash identifier->color
 * @param {number} context.depth - Depth in parenthesis, used for coloring
 * @returns {Element}
 */
ENode.prototype.toMathML = function(context) {
    var c0, c1, c2, c3, c4, i, j, el, par, mrow, mo, mtable, mfrac, msub, msup;
    if (typeof context == "undefined")
        context = { hcolors: {}, depth: 0 };
    if (this.children != null && this.children.length > 0)
        c0 = this.children[0];
    else
        c0 = null;
    if (this.children != null && this.children.length > 1)
        c1 = this.children[1];
    else
        c1 = null;
    if (this.children != null && this.children.length > 2)
        c2 = this.children[2];
    else
        c2 = null;
    if (this.children != null && this.children.length > 3)
        c3 = this.children[3];
    else
        c3 = null;
    if (this.children != null && this.children.length > 4)
        c4 = this.children[4];
    else
        c4 = null;
    
    switch (this.type) {
        case ENode.UNKNOWN:
            el = document.createElement('mtext');
            el.appendChild(document.createTextNode("???"));
            return(el);
        
        case ENode.NAME:
            if (this.value.search(/^[a-zA-Z]+[0-9]+$/) >= 0) {
                var ind = this.value.search(/[0-9]/);
                msub = document.createElement('msub');
                msub.appendChild(this.mi(this.value.substring(0,ind)));
                msub.appendChild(this.mn(this.value.substring(ind)));
                el = msub;
            } else {
                el = this.mi(this.value)
            }
            el.setAttribute("mathcolor", this.getColorForIdentifier(this.value, \
context.hcolors));  return(el);
        
        case ENode.NUMBER:
            if (this.value.indexOf('e') != -1 || this.value.indexOf('E') != -1) {
                var index = this.value.indexOf('e');
                if (index == -1)
                    index = this.value.indexOf('E');
                mrow = document.createElement('mrow');
                mrow.appendChild(this.mn(this.value.substring(0, index)));
                mrow.appendChild(this.mo("\u22C5"));
                msup = document.createElement('msup');
                msup.appendChild(this.mn(10));
                msup.appendChild(this.mn(this.value.substring(index + 1)));
                mrow.appendChild(msup);
                return(mrow);
            }
            return(this.mn(this.value));
        
        case ENode.OPERATOR:
            if (this.value == "/") {
                mfrac = document.createElement('mfrac');
                mfrac.appendChild(c0.toMathML(context));
                mfrac.appendChild(c1.toMathML(context));
                el = mfrac;
            } else if (this.value == "^") {
                if (c0.type == ENode.FUNCTION) {
                    if (c0.value == "sqrt" || c0.value == "abs" || c0.value == \
"matrix" ||  c0.value == "diff")
                        par = false;
                    else
                        par = true;
                } else if (c0.type == ENode.OPERATOR) {
                    par = true;
                } else
                    par = false;
                el = document.createElement('msup');
                if (par)
                    el.appendChild(this.addP(c0, context));
                else
                    el.appendChild(c0.toMathML(context));
                el.appendChild(c1.toMathML(context));
            } else if (this.value == "*") {
                mrow = document.createElement('mrow');
                if (c0.type == ENode.OPERATOR && (c0.value == "+" || c0.value == \
"-"))  mrow.appendChild(this.addP(c0, context));
                else
                    mrow.appendChild(c0.toMathML(context));
                // should the x operator be visible ? We need to check if there is a \
number to the left of c1  var firstinc1 = c1;
                while (firstinc1.type == ENode.OPERATOR) {
                    firstinc1 = firstinc1.children[0];
                }
                // ... and if it's an operation between vectors/matrices, the * \
operator should be displayed  // (it is ambiguous otherwise)
                // note: this will not work if the matrix is calculated, for instance \
with 2[1;2]*[3;4]  if (c0.type == ENode.VECTOR && c1.type == ENode.VECTOR)
                    mrow.appendChild(this.mo("*"));
                else if (firstinc1.type == ENode.NUMBER)
                    mrow.appendChild(this.mo("\u22C5"));
                if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                el = mrow;
            } else if (this.value == "-") {
                mrow = document.createElement('mrow');
                if (this.children.length == 1) {
                    mrow.appendChild(this.mo("-"));
                    mrow.appendChild(c0.toMathML(context));
                } else {
                    mrow.appendChild(c0.toMathML(context));
                    mrow.appendChild(this.mo("-"));
                    if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                    else
                        mrow.appendChild(c1.toMathML(context));
                }
                el = mrow;
            } else if (this.value == "!") {
                mrow = document.createElement('mrow');
                mo = this.mo(this.value);
                if (c0.type == ENode.OPERATOR && (c0.value == "+" || c0.value == \
"-"))  mrow.appendChild(this.addP(c0, context));
                else
                    mrow.appendChild(c0.toMathML(context));
                mrow.appendChild(mo);
                el = mrow;
            } else if (this.value == "+") {
                mrow = document.createElement('mrow');
                mo = this.mo(this.value);
                mrow.appendChild(c0.toMathML(context));
                mrow.appendChild(mo);
                // should we add parenthesis ? We need to check if there is a '-' to \
the left of c1  par = false;
                var first = c1;
                while (first.type == ENode.OPERATOR) {
                    if (first.value == "-" && first.children.length == 1) {
                        par = true;
                        break;
                    } else if (first.value == "+" || first.value == "-" || \
first.value == "*") {  first = first.children[0];
                    } else {
                        break;
                    }
                }
                if (par)
                    mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                el = mrow;
            } else if (this.value == ".") {
                mrow = document.createElement('mrow');
                if (c0.type == ENode.OPERATOR && (c0.value == "+" || c0.value == \
"-"))  mrow.appendChild(this.addP(c0, context));
                else
                    mrow.appendChild(c0.toMathML(context));
                mrow.appendChild(this.mo("\u22C5"));
                if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                el = mrow;
            } else if (this.value == "`") {
                mrow = document.createElement('mrow');
                if (c0.type == ENode.OPERATOR && (c0.value == "+" || c0.value == \
"-"))  mrow.appendChild(this.addP(c0, context));
                else
                    mrow.appendChild(c0.toMathML(context));
                // the units should not be in italics
                var mstyle = document.createElement("mstyle");
                mstyle.setAttribute("fontstyle", "normal");
                if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == \
"-"))  mstyle.appendChild(this.addP(c1, context));
                else
                    mstyle.appendChild(c1.toMathML(context));
                mrow.appendChild(mstyle);
                el = mrow;
            } else {
                // relational operators
                mrow = document.createElement('mrow');
                mo = this.mo(this.value);
                mrow.appendChild(c0.toMathML(context));
                mrow.appendChild(mo);
                mrow.appendChild(c1.toMathML(context));
                el = mrow;
            }
            return(el);
        
        case ENode.FUNCTION: /* TODO: throw exceptions if wrong nb of args ? */
            // c0 contains the function name
            if (c0.value == "sqrt" && c1 != null) {
                el = document.createElement('msqrt');
                el.appendChild(c1.toMathML(context));
            } else if (c0.value == "abs" && c1 != null) {
                mrow = document.createElement('mrow');
                mrow.appendChild(this.mo("|"));
                mrow.appendChild(c1.toMathML(context));
                mrow.appendChild(this.mo("|"));
                el = mrow;
            } else if (c0.value == "exp" && c1 != null) {
                el = document.createElement('msup');
                el.appendChild(this.mi("e"));
                el.appendChild(c1.toMathML(context));
            } else if (c0.value == "factorial") {
                mrow = document.createElement('mrow');
                mo = this.mo("!");
                if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                mrow.appendChild(mo);
                el = mrow;
            } else if (c0.value == "diff" && this.children != null && \
this.children.length == 3) {  mrow = document.createElement('mrow');
                mfrac = document.createElement('mfrac');
                mfrac.appendChild(this.mi("d"));
                var f2 = document.createElement('mrow');
                f2.appendChild(this.mi("d"));
                f2.appendChild(this.mi(c2.value));
                mfrac.appendChild(f2);
                mrow.appendChild(mfrac);
                if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                el = mrow;
            } else if (c0.value == "diff" && this.children != null && \
this.children.length == 4) {  mrow = document.createElement('mrow');
                mfrac = document.createElement('mfrac');
                msup = document.createElement('msup');
                msup.appendChild(this.mi("d"));
                msup.appendChild(c3.toMathML(context));
                mfrac.appendChild(msup);
                var f2 = document.createElement('mrow');
                f2.appendChild(this.mi("d"));
                msup = document.createElement('msup');
                msup.appendChild(c2.toMathML(context));
                msup.appendChild(c3.toMathML(context));
                f2.appendChild(msup);
                mfrac.appendChild(f2);
                mrow.appendChild(mfrac);
                if (c1.type == ENode.OPERATOR && (c1.value == "+" || c1.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                el = mrow;
            } else if (c0.value == "integrate" && this.children != null && \
this.children.length == 3) {  mrow = document.createElement('mrow');
                var mo = this.mo("\u222B");
                mo.setAttribute("stretchy", "true"); // doesn't work with MathJax
                mrow.appendChild(mo);
                if (c2.type == ENode.OPERATOR && (c2.value == "+" || c2.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                mrow.appendChild(this.mi("d"));
                mrow.appendChild(c2.toMathML(context));
                el = mrow;
            } else if (c0.value == "integrate" && this.children != null && \
this.children.length == 5) {  mrow = document.createElement('mrow');
                var msubsup = document.createElement('msubsup');
                var mo = this.mo("\u222B");
                mo.setAttribute("stretchy", "true"); // doesn't work with MathJax
                msubsup.appendChild(mo);
                msubsup.appendChild(c3.toMathML(context));
                msubsup.appendChild(c4.toMathML(context));
                mrow.appendChild(msubsup);
                if (c2.type == ENode.OPERATOR && (c2.value == "+" || c2.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                mrow.appendChild(this.mi("d"));
                mrow.appendChild(c2.toMathML(context));
                el = mrow;
            } else if (c0.value == "sum" && this.children != null && \
this.children.length == 5) {  mrow = document.createElement('mrow');
                var munderover = document.createElement('munderover');
                var mo = this.mo("\u2211");
                mo.setAttribute("stretchy", "true"); // doesn't work with MathJax
                munderover.appendChild(mo);
                var mrow2 = document.createElement('mrow');
                mrow2.appendChild(c2.toMathML(context));
                mrow2.appendChild(this.mo("="));
                mrow2.appendChild(c3.toMathML(context));
                munderover.appendChild(mrow2);
                munderover.appendChild(c4.toMathML(context));
                mrow.appendChild(munderover);
                if (c2.type == ENode.OPERATOR && (c2.value == "+" || c2.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                el = mrow;
            } else if (c0.value == "product" && this.children != null && \
this.children.length == 5) {  mrow = document.createElement('mrow');
                var munderover = document.createElement('munderover');
                var mo = this.mo("\u220F");
                mo.setAttribute("stretchy", "true"); // doesn't work with MathJax
                munderover.appendChild(mo);
                var mrow2 = document.createElement('mrow');
                mrow2.appendChild(c2.toMathML(context));
                mrow2.appendChild(this.mo("="));
                mrow2.appendChild(c3.toMathML(context));
                munderover.appendChild(mrow2);
                munderover.appendChild(c4.toMathML(context));
                mrow.appendChild(munderover);
                if (c2.type == ENode.OPERATOR && (c2.value == "+" || c2.value == \
"-"))  mrow.appendChild(this.addP(c1, context));
                else
                    mrow.appendChild(c1.toMathML(context));
                el = mrow;
            } else if (c0.value == "limit") {
                mrow = document.createElement('mrow');
                if (this.children.length < 4) {
                    mrow.appendChild(this.mo("lim"));
                } else {
                    var munder = document.createElement('munder');
                    munder.appendChild(this.mo("lim"));
                    var mrowunder = document.createElement('mrow');
                    mrowunder.appendChild(c2.toMathML(context));
                    mrowunder.appendChild(this.mo("\u2192"));
                    mrowunder.appendChild(c3.toMathML(context));
                    if (c4 != null) {
                        if (c4.value == "plus")
                            mrowunder.appendChild(this.mo("+"));
                        else if (c4.value == "minus")
                            mrowunder.appendChild(this.mo("-"));
                    }
                    munder.appendChild(mrowunder);
                    mrow.appendChild(munder);
                }
                mrow.appendChild(c1.toMathML(context));
                el = mrow;
            } else if (c0.value == "binomial") {
                // displayed like a vector
                mrow = document.createElement('mrow');
                mrow.appendChild(this.mo("("));
                mtable = document.createElement('mtable');
                for (i=1; i<this.children.length; i++) {
                    var mtr = document.createElement('mtr');
                    mtr.appendChild(this.children[i].toMathML(context));
                    mtable.appendChild(mtr);
                }
                mrow.appendChild(mtable);
                mrow.appendChild(this.mo(")"));
                el = mrow;
            } else if (c0.value == "matrix") {
                for (i=1; i<this.children.length; i++) {
                    // check that all children are vectors
                    if (this.children[i].type !== ENode.VECTOR) {
                        el = document.createElement('mtext');
                        el.appendChild(document.createTextNode("???")); // could \
throw here  return(el);
                    }
                }
                mrow = document.createElement('mrow');
                mrow.appendChild(this.mo("("));
                mtable = document.createElement('mtable');
                for (i=1; i<this.children.length; i++) {
                    var mtr = document.createElement('mtr');
                    for (j=0; j<this.children[i].children.length; j++) {
                        \
mtr.appendChild(this.children[i].children[j].toMathML(context));  }
                    mtable.appendChild(mtr);
                }
                mrow.appendChild(mtable);
                mrow.appendChild(this.mo(")"));
                el = mrow;
            } else {
                // default display for a function
                mrow = document.createElement('mrow');
                mrow.appendChild(c0.toMathML(context));
                mrow.appendChild(this.mo("("));
                for (i=1; i<this.children.length; i++) {
                    mrow.appendChild(this.children[i].toMathML(context));
                    if (i < this.children.length - 1)
                        mrow.appendChild(this.mo(Definitions.ARG_SEPARATOR));
                }
                mrow.appendChild(this.mo(")"));
                el = mrow;
            }
            return(el);
        
        case ENode.VECTOR:
            var is_matrix = true;
            for (i=0; i<this.children.length; i++) {
                if (this.children[i].type !== ENode.VECTOR)
                    is_matrix = false;
            }
            mrow = document.createElement('mrow');
            mrow.appendChild(this.mo("("));
            mtable = document.createElement('mtable');
            for (i=0; i<this.children.length; i++) {
                var mtr = document.createElement('mtr');
                if (is_matrix) {
                    for (j=0; j<this.children[i].children.length; j++) {
                        \
mtr.appendChild(this.children[i].children[j].toMathML(context));  }
                } else {
                    mtr.appendChild(this.children[i].toMathML(context));
                }
                mtable.appendChild(mtr);
            }
            mrow.appendChild(mtable);
            mrow.appendChild(this.mo(")"));
            return(mrow);
            
        case ENode.SUBSCRIPT:
            msub = document.createElement('msub');
            msub.appendChild(c0.toMathML(context));
            if (this.children.length > 2) {
                mrow = document.createElement('mrow');
                for (i=1; i<this.children.length; i++) {
                    mrow.appendChild(this.children[i].toMathML(context));
                    if (i < this.children.length - 1)
                        mrow.appendChild(this.mo(Definitions.ARG_SEPARATOR));
                }
                msub.appendChild(mrow);
            } else {
                msub.appendChild(c1.toMathML(context));
            }
            return(msub);
    }
};

/**
 * Creates a MathML mi element with the given name
 * @param {string} name
 * @returns {Element}
 */
ENode.prototype.mi = function(name) {
    var mi = document.createElement('mi');
    if (ENode.symbols[name])
        name = ENode.symbols[name];
    mi.appendChild(document.createTextNode(name));
    return mi;
};

/**
 * Creates a MathML mn element with the given number or string
 * @param {string} n
 * @returns {Element}
 */
ENode.prototype.mn = function(n) {
    var mn = document.createElement('mn');
    mn.appendChild(document.createTextNode(n));
    return mn;
};

/**
 * Creates a MathML mo element with the given name
 * @param {string} name
 * @returns {Element}
 */
ENode.prototype.mo = function(name) {
    var mo = document.createElement('mo');
    if (ENode.symbols[name])
        name = ENode.symbols[name];
    mo.appendChild(document.createTextNode(name));
    return mo;
};

/**
 * Add parenthesis and returns a MathML element
 * @param {ENode} en
 * @param {Object} [context] - display context (not needed for the root element)
 * @param {Object.<string, string>} context.hcolors - hash identifier->color
 * @param {number} context.depth - Depth in parenthesis, used for coloring
 * @returns {Element}
 */
ENode.prototype.addP = function(en, context) {
    var mrow, mo;
    mrow = document.createElement('mrow');
    mo = this.mo("(");
    mo.setAttribute("mathcolor", ENode.COLORS[context.depth % ENode.COLORS.length]);
    mrow.appendChild(mo);
    context.depth++;
    mrow.appendChild(en.toMathML(context));
    context.depth--;
    mo = this.mo(")");
    mo.setAttribute("mathcolor", ENode.COLORS[context.depth % ENode.COLORS.length]);
    mrow.appendChild(mo);
    return mrow;
};

ENode.symbols = {
    /* lowercase greek */
    "alpha": "\u03B1", "beta": "\u03B2", "gamma": "\u03B3",
    "delta": "\u03B4", "epsilon": "\u03B5", "zeta": "\u03B6",
    "eta": "\u03B7", "theta": "\u03B8", "iota": "\u03B9",
    "kappa": "\u03BA", "lambda": "\u03BB", "mu": "\u03BC",
    "nu": "\u03BD", "xi": "\u03BE", "omicron": "\u03BF",
    "pi": "\u03C0", "rho": "\u03C1", "sigma": "\u03C3",
    "tau": "\u03C4", "upsilon": "\u03C5", "phi": "\u03C6",
    "chi": "\u03C7", "psi": "\u03C8", "omega": "\u03C9",
    /* uppercase greek */
    "Alpha": "\u0391", "Beta": "\u0392", "Gamma": "\u0393",
    "Delta": "\u0394", "Epsilon": "\u0395", "Zeta": "\u0396",
    "Eta": "\u0397", "Theta": "\u0398", "Iota": "\u0399",
    "Kappa": "\u039A", "Lambda": "\u039B", "Mu": "\u039C",
    "Nu": "\u039D", "Xi": "\u039E", "Omicron": "\u039F",
    "Pi": "\u03A0", "Rho": "\u03A1", "Sigma": "\u03A3",
    "Tau": "\u03A4", "Upsilon": "\u03A5", "Phi": "\u03A6",
    "Chi": "\u03A7", "Psi": "\u03A8", "Omega": "\u03A9",
    
    /* operators */
    "#":  "\u2260",
    ">=": "\u2265",
    "<=": "\u2264",
    
    /* other */
    "inf":  "\u221E",
    "minf": "-\u221E",
    "hbar": "\u210F",
    "G":    "\uD835\uDCA2" // 1D4A2
};

Index: loncom/html/adm/LC_math_editor/src/operator.js
+++ loncom/html/adm/LC_math_editor/src/operator.js
/*

Copyright (C) 2014  Michigan State University Board of Trustees

The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version.  The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.

As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.

*/

/**
 * Null denotation function
 * @callback nudFunction
 * @param {Parser} p - the parser
 * @returns {ENode}
 */

/**
 * Left denotation function
 * @callback ledFunction
 * @param {Parser} p - the parser
 * @param {ENode} left - left node
 * @returns {ENode}
 */

/**
 * Parser operator, like "(".
 * @constructor
 * @param {string} id - Characters used to recognize the operator
 * @param {number} arity (UNKNOWN, UNARY, BINARY, TERNARY)
 * @param {number} lbp - left binding power
 * @param {number} rbp - right binding power
 * @param {nudFunction} nud - Null denotation function
 * @param {ledFunction} led - Left denotation function
 */
function Operator(id, arity, lbp, rbp, nud, led) {
    this.id = id;
    this.arity = arity;
    this.lbp = lbp;
    this.rbp = rbp;
    this.nud = nud;
    this.led = led;
}

Operator.UNKNOWN = 0;
Operator.UNARY = 1;
Operator.BINARY = 2;
Operator.TERNARY = 3;

Index: loncom/html/adm/LC_math_editor/src/parse_exception.js
+++ loncom/html/adm/LC_math_editor/src/parse_exception.js
/*

Copyright (C) 2014  Michigan State University Board of Trustees

The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version.  The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.

As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.

*/

/**
 * Parse exception
 * @constructor
 * @param {string} msg - Error message
 * @param {number} from - Character index
 * @param {number} [to] - Character index to (inclusive)
 */
function ParseException(msg, from, to) {
    this.msg = msg;
    this.from = from;
    if (to)
        this.to = to;
    else
        this.to = this.from;
}

/**
 * Returns the exception as a string, for debug
 * @returns {string}
 */
ParseException.prototype.toString = function() {
    return(this.msg + " at " + this.from + " - " + this.to);
};

Index: loncom/html/adm/LC_math_editor/src/parser.js
+++ loncom/html/adm/LC_math_editor/src/parser.js
/*

Copyright (C) 2014  Michigan State University Board of Trustees

The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version.  The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.

As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.

*/

/**
 * Equation parser
 * @constructor
 * @param {boolean} [implicit_operators] - assume hidden multiplication and unit \
                operators in some cases (unlike maxima)
 * @param {boolean} [unit_mode] - handle only numerical expressions with units (no \
                variable)
 * @param {Array.<string>} [constants] - array of constant names for unit mode
 */
function Parser(implicit_operators, unit_mode, constants) {
    if (typeof implicit_operators == "undefined")
        this.implicit_operators = false;
    else
        this.implicit_operators = implicit_operators;
    if (typeof unit_mode == "undefined")
        this.unit_mode = false;
    else
        this.unit_mode = unit_mode;
    if (typeof constants == "undefined")
        this.constants = [];
    else
        this.constants = constants;
    this.defs = new Definitions();
    this.defs.define();
    this.operators = this.defs.operators;
    this.oph = {}; // operator hash table
    for (var i=0; i<this.operators.length; i++)
        this.oph[this.operators[i].id] = this.operators[i];
}

/**
 * Returns the right node at the current token, based on top-down operator \
                precedence.
 * @param {number} rbp - Right binding power
 * @returns {ENode}
 */
Parser.prototype.expression = function(rbp) {
    var left; // ENode
    var t = this.current_token;
    if (t == null)
        throw new ParseException("Expected something at the end",
            this.tokens[this.tokens.length - 1].to + 1);
    this.advance();
    if (t.op == null)
        left = new ENode(t.type, null, t.value, null);
    else if (t.op.nud == null)
        throw new ParseException("Unexpected operator '" + t.op.id + "'", t.from);
    else
        left = t.op.nud(this);
    while (this.current_token != null && this.current_token.op != null &&
            rbp < this.current_token.op.lbp) {
        t = this.current_token;
        this.advance();
        left = t.op.led(this, left);
    }
    return left;
};

/**
 * Advance to the next token,
 * expecting the given operator id if it is provided.
 * Throws a ParseException if a given operator id is not found.
 * @param {string} [id] - Operator id
 */
Parser.prototype.advance = function(id) {
    if (id && (this.current_token == null || this.current_token.op == null ||
            this.current_token.op.id !== id)) {
        if (this.current_token == null)
            throw new ParseException("Expected '" + id + "' at the end",
                this.tokens[this.tokens.length - 1].to + 1);
        else
            throw new ParseException("Expected '" + id + "'", \
this.current_token.from);  }
    if (this.token_nr >= this.tokens.length) {
        this.current_token = null;
        return;
    }
    this.current_token = this.tokens[this.token_nr];
    this.token_nr += 1;
};

/**
 * Adds hidden multiplication and unit operators to the token stream
 */
Parser.prototype.addHiddenOperators = function() {
    var multiplication = this.defs.findOperator("*");
    var unit_operator = this.defs.findOperator("`");
    var in_units = false; // we check if we are already in the units to avoid adding \
two ` operators inside  var in_exp = false;
    for (var i=0; i<this.tokens.length - 1; i++) {
        var token = this.tokens[i];
        var next_token = this.tokens[i + 1];
        if (this.unit_mode) {
            if (token.value == "`")
                in_units = true;
            else if (in_units) {
                if (token.value == "^")
                    in_exp = true;
                else if (in_exp && token.type == Token.NUMBER)
                    in_exp = false;
                else if (!in_exp && token.type == Token.NUMBER)
                    in_units = false;
                else if (token.type == Token.OPERATOR && "*/^()".indexOf(token.value) \
== -1)  in_units = false;
                else if (token.type == Token.NAME && next_token.value == "(")
                    in_units = false;
            }
        }
        if (
                (token.type == Token.NAME && next_token.type == Token.NAME) ||
                (token.type == Token.NUMBER && next_token.type == Token.NAME) ||
                (token.type == Token.NUMBER && next_token.type == Token.NUMBER) ||
                (token.type == Token.NUMBER && (next_token.value == "(" || \
                next_token.value == "[")) ||
                /*(token.type == Token.NAME && next_token.value == "(") ||*/
                /* name ( could be a function call */
                ((token.value == ")" || token.value == "]") && next_token.type == \
                Token.NAME) ||
                ((token.value == ")" || token.value == "]") && next_token.type == \
                Token.NUMBER) ||
                ((token.value == ")" || token.value == "]") && next_token.value == \
"(")  ) {
            // support for things like "(1/2) (m/s)" is complex...
            var units = (this.unit_mode && !in_units && (token.type == Token.NUMBER \
||  (token.value == ")" || token.value == "]")) &&
                (next_token.type == Token.NAME ||
                    ((next_token.value == "(" || next_token.value == "[") && \
this.tokens.length > i + 2 &&  this.tokens[i + 2].type == Token.NAME)));
            if (units) {
                var test_token, index_test;
                if (next_token.type == Token.NAME) {
                    test_token = next_token;
                    index_test = i + 1;
                } else {
                    // for instance for "2 (m/s)"
                    index_test = i + 2;
                    test_token = this.tokens[index_test];
                }
                for (var j=0; j<this.constants.length; j++) {
                    if (test_token.value == this.constants[j]) {
                        units = false;
                        break;
                    }
                }
                if (this.tokens.length > index_test + 1 && this.tokens[index_test + \
                1].value == "(") {
                    var known_functions = ["pow", "sqrt", "abs", "exp", "factorial", \
                "diff",
                        "integrate", "sum", "product", "limit", "binomial", "matrix",
                        "ln", "log", "log10", "mod", "signum", "ceiling", "floor",
                        "sin", "cos", "tan", "asin", "acos", "atan", "atan2",
                        "sinh", "cosh", "tanh", "asinh", "acosh", "atanh"];
                    for (var j=0; j<known_functions.length; j++) {
                        if (test_token.value == known_functions[j]) {
                            units = false;
                            break;
                        }
                    }
                }
            }
            var new_token;
            if (units) {
                new_token = new Token(Token.OPERATOR, next_token.from,
                    next_token.from, unit_operator.id, unit_operator);
                in_units = true;
            } else {
                new_token = new Token(Token.OPERATOR, next_token.from,
                    next_token.from, multiplication.id, multiplication);
            }
            this.tokens.splice(i+1, 0, new_token);
        }
    }
}

/**
 * Parse the string, returning an ENode tree.
 * @param {string} text - The text to parse.
 * @returns {ENode}
 */
Parser.prototype.parse = function(text) {
    var tokenizer = new Tokenizer(this.defs, text);
    this.tokens = tokenizer.tokenize();
    if (this.tokens.length == 0) {
        return null;
    }
    if (this.implicit_operators) {
        this.addHiddenOperators();
    }
    this.token_nr = 0;
    this.current_token = this.tokens[this.token_nr];
    this.advance();
    var root = this.expression(0);
    if (this.current_token != null) {
        throw new ParseException("Expected the end", this.current_token.from);
    }
    return root;
};

Index: loncom/html/adm/LC_math_editor/src/token.js
+++ loncom/html/adm/LC_math_editor/src/token.js
/*

Copyright (C) 2014  Michigan State University Board of Trustees

The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version.  The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.

As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.

*/

/**
 * A token from the equation text.
 * @constructor
 * @param {number} type - Token type: Token.UNKNOWN, NAME, NUMBER, OPERATOR
 * @param {number} from - Index of the token's first character
 * @param {number} to - Index of the token's last character
 * @param {string} value - String content of the token
 * @param {Operator} op - The matching operator, possibly null
 */
function Token(type, from, to, value, op) {
    this.type = type;
    this.from = from;
    this.to = to;
    this.value = value;
    this.op = op;
}

Token.UNKNOWN = 0;
Token.NAME = 1;
Token.NUMBER = 2;
Token.OPERATOR = 3;

Index: loncom/html/adm/LC_math_editor/src/tokenizer.js
+++ loncom/html/adm/LC_math_editor/src/tokenizer.js
/*

Copyright (C) 2014  Michigan State University Board of Trustees

The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version.  The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.

As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.

*/

/**
 * String tokenizer. Recognizes only names, numbers, and parser operators.
 * @constructor
 * @param {Definitions} defs - Operator definitions
 * @param {string} text - The text to tokenize
 */
function Tokenizer(defs, text) {
    this.defs = defs;
    this.text = text;
}

/**
 * Tokenizes the text.
 * Can throw a ParseException.
 * @returns {Array.<Token>}
 */
Tokenizer.prototype.tokenize = function() {
    var c, i, iop, from, tokens, value;
    
    i = 0;
    c = this.text.charAt(i);
    tokens = [];
    
main:
    while (c) {
        from = i;
        
        // ignore whitespace
        if (c <= ' ') {
            i++;
            c = this.text.charAt(i);
            continue;
        }
        
        // check for numbers before operators
        // (numbers starting with . will not be confused with the . operator)
        if ((c >= '0' && c <= '9') ||
                ((c === Definitions.DECIMAL_SIGN_1 || c === \
                Definitions.DECIMAL_SIGN_2) &&
                (this.text.charAt(i+1) >= '0' && this.text.charAt(i+1) <= '9'))) {
            value = '';
            
            if (c !== Definitions.DECIMAL_SIGN_1 && c !== Definitions.DECIMAL_SIGN_2) \
{  i++;
                value += c;
                // Look for more digits.
                for (;;) {
                    c = this.text.charAt(i);
                    if (c < '0' || c > '9') {
                        break;
                    }
                    i++;
                    value += c;
                }
            }
            
            // Look for a decimal fraction part.
            if (c === Definitions.DECIMAL_SIGN_1 || c === Definitions.DECIMAL_SIGN_2) \
{  i++;
                value += c;
                for (;;) {
                    c = this.text.charAt(i);
                    if (c < '0' || c > '9') {
                        break;
                    }
                    i += 1;
                    value += c;
                }
            }
            
            // Look for an exponent part.
            if (c === 'e' || c === 'E') {
                i++;
                value += c;
                c = this.text.charAt(i);
                if (c === '-' || c === '+') {
                    i++;
                    value += c;
                    c = this.text.charAt(i);
                }
                if (c < '0' || c > '9') {
                    // syntax error in number exponent
                    throw new ParseException("syntax error in number exponent", from, \
i);  }
                do {
                    i++;
                    value += c;
                    c = this.text.charAt(i);
                } while (c >= '0' && c <= '9');
            }
            
            /* this is not necessary, as the parser will not recognize the tokens
               if it is not accepted, and if bad syntax is accepted a * operator will \
be added  // Make sure the next character is not a letter.
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
                // syntax error in number
                throw new ParseException("syntax error in number", from, i);
            }
            */
            
            // Convert the string value to a number. If it is finite, then it is a \
                good token.
            var n = +value.replace(Definitions.DECIMAL_SIGN_1, \
'.').replace(Definitions.DECIMAL_SIGN_2, '.');  if (isFinite(n)) {
                tokens.push(new Token(Token.NUMBER, from, i - 1, value, null));
                continue;
            } else {
                // syntax error in number
                throw new ParseException("syntax error in number", from, i);
            }
        }
        
        // check for operators before names (they could be confused with
        // variables if they don't use special characters)
        for (iop = 0; iop < this.defs.operators.length; iop++) {
            var op = this.defs.operators[iop];
            if (this.text.substring(i, i+op.id.length) === op.id) {
                i += op.id.length;
                c = this.text.charAt(i);
                tokens.push(new Token(Token.OPERATOR, from, i - 1, op.id, op));
                continue main;
            }
        }
        
        // names
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
            value = c;
            i++;
            for (;;) {
                c = this.text.charAt(i);
                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
                        (c >= '0' && c <= '9') || c === '_') {
                    value += c;
                    i++;
                } else {
                    break;
                }
            }
            tokens.push(new Token(Token.NAME, from, i - 1, value, null));
            continue;
        }
        
        // unrecognized operator
        throw new ParseException("unrecognized operator", from, i);
    }
    return tokens;
};

Index: loncom/html/adm/LC_math_editor/src/ui.js
+++ loncom/html/adm/LC_math_editor/src/ui.js
/*

Copyright (C) 2014  Michigan State University Board of Trustees

The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version.  The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.

As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.

*/

"use strict";

var handleChange = function(math_object) {
    // math_object has 3 fields: ta, output_node, oldtxt
    // we need to pass this object instead of the values because oldtxt will change
    var ta, output_node, txt, parser, output, root, test1, test2;
    ta = math_object.ta;
    output_node = math_object.output_node;
    txt = ta.value;
    
    // automatically add brackets to something like "1;2;3", for LON-CAPA:
    // NOTE: this is ugly and sometimes adds brackets to error messages
    test1 = '';
    test2 = txt;
    while (test2 != test1) {
      test1 = test2;
      test2 = test1.replace(/\[[^\[\]]*\]/g, '');
    }
    if (test2.split("[").length == test2.split("]").length) {
      test1 = '';
      while (test2 != test1) {
        test1 = test2;
        test2 = test1.replace(/\([^\(\)]*\)/g, '');
      }
      if (test2.split("(").length == test2.split(")").length) {
        if (test2.indexOf(Definitions.ARG_SEPARATOR) != -1) {
          txt = '['+txt+']';
        }
      }
    }
    
    if (txt != math_object.oldtxt) {
        math_object.oldtxt = txt;
        while (output_node.firstChild != null)
            output_node.removeChild(output_node.firstChild);
        output_node.removeAttribute("title");
        if (txt != "") {
            parser = math_object.parser;
            try {
                root = parser.parse(txt);
                if (root != null) {
                    var math = document.createElement("math");
                    math.setAttribute("display", "block");
                    math.appendChild(root.toMathML());
                    output_node.appendChild(math);
                    MathJax.Hub.Queue(["Typeset", MathJax.Hub, output_node]);
                }
            } catch (e) {
                output = "error: " + e;
                output_node.setAttribute("title", output);
                if (e instanceof ParseException) {
                    output_node.appendChild(document.createTextNode(txt.substring(0, \
e.from)));  var span = document.createElement('span');
                    span.appendChild(document.createTextNode(txt.substring(e.from, \
e.to + 1)));  span.className = 'math-error';
                    output_node.appendChild(span);
                    if (e.to < txt.length - 1) {
                        \
output_node.appendChild(document.createTextNode(txt.substring(e.to + 1)));  }
                } else {
                    var tn = document.createTextNode(output);
                    output_node.appendChild(tn);
                }
            }
        }
    }
}

var init_done = false;

/*
  Looks for elements with the "math" class, and
  adds a preview div afterward which is updated automatically.
*/
var initEditors = function() {
    if (init_done)
        return;
    init_done = true;
    var math_objects = [];
    var math_inputs = document.getElementsByClassName('math');
    for (var i=0; i<math_inputs.length; i++) {
        var ta = math_inputs[i];
        if (ta.nodeName == "TEXTAREA" || ta.nodeName == "INPUT") {
            var output_node = document.createElement("span");
            output_node.setAttribute("style", "display:none");
            if (ta.nextSibling)
                ta.parentNode.insertBefore(output_node, ta.nextSibling);
            else
                ta.parentNode.appendChild(output_node);
            var hideNode = function(node) {
                return function(e) { node.setAttribute("style", "display:none"); };
            };
            var showNode = function(node) {
                return function(e) { node.setAttribute("style", "display: \
inline-block; background-color: #FFFFE0"); };  };
            ta.addEventListener("blur", hideNode(output_node), false);
            ta.addEventListener("focus", showNode(output_node), false);
            var implicit_operators = (ta.getAttribute("data-implicit_operators") === \
                "true");
            var unit_mode = (ta.getAttribute("data-unit_mode") === "true");
            var constants = ta.getAttribute("data-constants");
            if (constants)
                constants = constants.split(/[\s,]+/);
            var oldtxt = "";
            math_objects[i] = {
                "ta": ta,
                "output_node": output_node,
                "oldtxt": oldtxt,
                "parser": new Parser(implicit_operators, unit_mode, constants)
            };
            var changeObjectN = function(n) {
                return function(e) {
                  var obj = math_objects[n];
                  if (document.activeElement == obj.ta) {
                    // this is useful if there is data in the field with the page \
                default focus
                    // (there might not be a focus event for the active element)
                    obj.output_node.setAttribute("style", "display: inline-block; \
background-color: #FFFFE0");  }
                  handleChange(obj);
                };
            };
            var startChange = changeObjectN(i);
            if (ta.value != oldtxt)
                startChange(); // process non-empty fields even though they are not \
visible yet  ta.addEventListener('change', startChange, false);
            ta.addEventListener('keyup', startChange, false);
        }
    }
}



_______________________________________________
LON-CAPA-cvs mailing list
LON-CAPA-cvs@mail.lon-capa.org
http://mail.lon-capa.org/mailman/listinfo/lon-capa-cvs


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic