[LON-CAPA-cvs] cvs: loncom /build compressjs.sh /homework response.pm /html/adm/LC_math_editor build.sh test.html /html/adm/LC_math_editor/dist LC_math_editor.min.js /html/adm/LC_math_editor/src definitions.js enode.js operator.js parse_exception.js parser.js token.js tokenizer.js ui.js doc/loncapafiles loncapafiles.lpml
damieng
damieng at source.lon-capa.org
Wed Sep 24 14:14:39 EDT 2014
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
-------------- next part --------------
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.tokens[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);this.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)for(;;){b.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.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.children[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.appendChild(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.createElement("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.value||"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.appendChild(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.appendChild(this.mo("-")),d.appendChild(b.toMathML(a))):(d.appendChild(b.toMathML(a)),d.appendChild(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.appendChild(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.appendChild(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.value?(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=document.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.OPERATOR||"+"!=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.OPERATOR||"+"!=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.createElement("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.createElement("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.createElement("mrow"),b=document.createElement("msubsup"),h=this.mo("\u222b"),h.setAttribute("stretchy","true"),b.appendChild(h),b.appendChild(l.toMathML(a)),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)),d.appendChild(this.mi("d")),d.appendChild(f.toMathML(a));else if("sum"==b.value&&null!=this.children&&5==this.children.length)d=document.createElement("mrow"),b=document.createElement("munderover"),h=this.mo("\u2211"),h.setAttribute("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("product"==b.value&&null!=this.children&&5==this.children.length)d=document.createElement("mrow"),b=document.createElement("munderover"),h=this.mo("\u220f"),h.setAttribute("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.appendChild(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=document.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.createElement("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.mo("(")),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.createElement("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.children.length){d=document.createElement("mrow");for(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));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.COLORS[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:"\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",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","#":"\u2260",">=":"\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.current_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_token=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.NAME&&"("==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_token=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.DECIMAL_SIGN_2)&&"0"<=this.text.charAt(b+1)&&"9">=this.text.charAt(b+1)){c="";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++;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.oldtxt){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.toMathML());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.createElement("span"),f.appendChild(document.createTextNode(e.substring(l.from,l.to+1))),f.className="math-error",c.appendChild(f),l.to<e.length-1&&c.appendChild(document.createTextNode(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);
}
}
}
More information about the LON-CAPA-cvs
mailing list