[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