-
-
Save hogeika/1921717 to your computer and use it in GitHub Desktop.
EquationPad
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<style type="text/css"> | |
.selectedTarget { background-color: red} | |
</style> | |
</head> | |
<body onload="onload()"> | |
<div id="history" contenteditable="true"></div> | |
<script language="javascript"> | |
var g_VERVOSE = false; | |
function assertFail(msg) { | |
if(g_VERVOSE) | |
alert("fail! "+ msg); | |
throw msg; | |
} | |
function assertEq(exp, act) { | |
if(exp != act) | |
assertFail("not equal [" + exp + "] and [" + act + "]"); | |
} | |
function assertNeq(exp, act) { | |
if(exp == act) | |
assertFail("not equal [" + exp + "] and [" + act + "]"); | |
} | |
function $(id) { | |
return document.getElementById(id); | |
} | |
function CE(tag){ | |
return document.createElement(tag); | |
} | |
var g_id = 1; | |
function IdGen() { | |
return g_id++; | |
} | |
function Mo(id, val) { | |
return "<mo id=\"" + id + "\">" + val + "</mo>"; | |
} | |
function Mi(id, val) { | |
return "<mi id=\"" + id + "\">" + val + "</mi>"; | |
} | |
function MnOrMi(term) { | |
if(term instanceof Number){ | |
return "<mn id=\"" + term.objId + "\">" + term + "</mn>"; | |
} | |
if(term instanceof String){ | |
if(term[0] == '&') { | |
return Mi(term.objId, term); | |
} | |
return Mi(term.objId, term); | |
} | |
throw "Unknow terminal"; | |
} | |
function EvalTerm(builder, term, nofence){ | |
if(term instanceof Array){ | |
if(!nofence) { | |
builder.push("<mfenced id=\""); | |
builder.push(term.objId); | |
builder.push("\"><mrow>"); | |
} | |
builder.push(JsonToMathML(term)); | |
if(!nofence) | |
builder.push("</mrow></mfenced>"); | |
}else{ | |
builder.push(MnOrMi(term)); | |
} | |
} | |
function PushMo(builder, tagName) { | |
builder.push("<mo>"); | |
builder.push(tagName); | |
builder.push("</mo>"); | |
} | |
function IsMinus(term) { | |
return term instanceof Array && term[0] == "-"; | |
} | |
function PushMinusTerm(builder, term) { | |
assertEq(true, IsMinus(term)); | |
PushMo(builder, "-"); | |
EvalTerm(builder,term[1]); | |
} | |
function EvalPlus(json) { | |
var builder = []; | |
var len = json.length; | |
if(IsMinus(json[1])){ | |
PushMinusTerm(builder, json[1]); | |
} | |
else | |
EvalTerm(builder, json[1]); | |
for(var i = 2; i < len; i++) { | |
if(IsMinus(json[i])){ | |
PushMinusTerm(builder, json[i]); | |
} | |
else { | |
PushMo(builder, "+"); | |
EvalTerm(builder,json[i]); | |
} | |
} | |
return builder.join(""); | |
} | |
function EvalTimes(json) { | |
var builder = []; | |
var len = json.length; | |
EvalTerm(builder, json[1]); | |
for(var i = 2; i < len; i++) { | |
if(json[i-1] instanceof Number && json[i] instanceof Number) | |
PushMo(builder, "×"); | |
EvalTerm(builder,json[i]); | |
} | |
return builder.join(""); | |
} | |
function EvalEqual(json) { | |
var builder = []; | |
EvalTerm(builder, json[1], true); | |
builder.push(Mo(json[0].objId, "=")); | |
EvalTerm(builder,json[2], true); | |
return builder.join(""); | |
} | |
function EvalPower(json) { | |
var builder = []; | |
builder.push("<msup>"); | |
builder.push("<mrow>"); | |
EvalTerm(builder, json[1]); | |
builder.push("</mrow>"); | |
builder.push("<mrow>"); | |
EvalTerm(builder, json[2], true); | |
builder.push("</mrow>"); | |
builder.push("</msup>"); | |
return builder.join(""); | |
} | |
function EvalDotOver(json) { | |
var builder = []; | |
builder.push("<mover>"); | |
EvalTerm(builder, json[1]); | |
builder.push("<mo>.</mo>"); | |
builder.push("</mover>"); | |
return builder.join(""); | |
} | |
function EvalIntegral(json) { | |
var builder = []; | |
if(json.length == 3) { | |
builder.push(Mo(json[0].objId, "∫")); | |
EvalTerm(builder, json[1], true); | |
// currently, json[2] does'nt support ρ, etc. | |
builder.push(Mi(json[2].objId, "d" + json[2] )); | |
} else if(json.length == 5) { | |
builder.push("<msubsup>"); | |
builder.push(Mo(json[0].objId, "∫")); | |
EvalTerm(builder, json[3], true); | |
EvalTerm(builder, json[4], true); | |
builder.push("</msubsup>"); | |
EvalTerm(builder, json[1], true); | |
// currently, json[2] does'nt support ρ, etc. | |
builder.push(Mi(json[2].objId, "d" + json[2] )); | |
} else { | |
throw "unknown arg number of integral. ( " + json.length + ")"; | |
} | |
return builder.join(""); | |
} | |
function JsonToMathML(json) { | |
var tag = json[0]; | |
if(tag == "-") | |
{ | |
var builder = []; | |
PushMinusTerm(builder, json); | |
return builder.join(""); | |
} | |
if(tag == "+") | |
{ | |
return EvalPlus(json); | |
} | |
if(tag == "*") | |
{ | |
return EvalTimes(json); | |
} | |
if(tag == "pow") | |
{ | |
return EvalPower(json); | |
} | |
if(tag == "dotover") | |
{ | |
return EvalDotOver(json); | |
} | |
if(tag == "int") | |
{ | |
return EvalIntegral(json); | |
} | |
if(tag == "=") | |
{ | |
return EvalEqual(json); | |
} | |
throw "unknown tag"; | |
} | |
function dp(st) { | |
var con = $("console"); | |
con.value += st + "\n"; | |
} | |
function EncloseMath(str,isBlock){ | |
var header ="<math " + (isBlock?"display=\"block\"":"") + "xmlns=\"http://www.w3.org/1998/Math/MathML\">" | |
var footer ="</math>" | |
return header + str + footer; | |
} | |
function showMath(str) { | |
var math = $("mathcanvas"); | |
math.innerHTML = EncloseMath(str,true); | |
} | |
function onClear() { | |
var con = $("console"); | |
con.value = ''; | |
} | |
var g_hash = {}; | |
function JsonParser(str) { | |
var sexp = eval(str); | |
var res = []; | |
res.objId = IdGen(); | |
g_hash[res.objId] = res; | |
for(var i = 0; i < sexp.length; i++) { | |
var obj; | |
if(typeof(sexp[i]) == "number") | |
obj = new Number(sexp[i]); | |
else if(typeof(sexp[i]) == "string") | |
obj = new String(sexp[i]); | |
else if(sexp[i] instanceof Array) | |
obj = JsonParser(sexp[i]); | |
else | |
obj = sexp[i]; | |
obj.objId = IdGen(); | |
g_hash[obj.objId] = obj; | |
res.push(obj); | |
obj.parent = res; | |
} | |
assertNeq(res, sexp); | |
return res; | |
} | |
var g_sexp; | |
function clicked() { | |
g_sexp = JsonParser($("input").value); | |
var result = JsonToMathML(g_sexp); | |
dp(result); | |
showMath(result); | |
} | |
function UpdateSelection() { | |
if(g_selected) { | |
var elem = $(g_selected.obj.objId); | |
elem.setAttribute("class", "selectedTarget"); | |
g_selected.elem = elem; | |
} | |
} | |
function InnerSexpSerializer(sexp, builder) { | |
if(sexp instanceof Number) { | |
builder.push(sexp.toString()); | |
return; | |
} | |
if(sexp instanceof String) { | |
builder.push('"'); | |
builder.push(sexp); | |
builder.push('"'); | |
return; | |
} | |
if(sexp instanceof Array) { | |
builder.push("["); | |
for(var i = 0; i < sexp.length; i++) { | |
if(i != 0) { | |
builder.push(","); | |
} | |
InnerSexpSerializer(sexp[i], builder); | |
} | |
builder.push("]"); | |
return; | |
} | |
alert(typeof(sexp)); | |
throw "unknown atom type inside S Expression Serializer"; | |
} | |
function SexpSerializer(sexp) { | |
var builder = []; | |
InnerSexpSerializer(sexp, builder); | |
return builder.join(""); | |
} | |
function Update(sexp) { | |
var result = JsonToMathML(sexp); | |
dp(result); | |
showMath(result); | |
UpdateSelection(); | |
$("input").value = SexpSerializer(sexp); | |
} | |
function FindIndex(arr, obj) { | |
for(var i = 0; i < arr.length; i++) { | |
if(arr[i] == obj) | |
return i; | |
} | |
return -1; | |
} | |
function MoveLeftTerm(sel) { | |
var parent = sel.obj.parent; | |
var i = FindIndex(parent, sel.obj); | |
if(i == 1) | |
return; | |
assertEq(parent[i], sel.obj); | |
var prev = parent[i-1]; | |
parent[i] = prev; | |
parent[i-1] = sel.obj; | |
} | |
function MoveRightTerm(sel) { | |
var parent = sel.obj.parent; | |
var i = FindIndex(parent, sel.obj); | |
if(i == parent.length -1 ) | |
return; | |
assertEq(parent[i], sel.obj); | |
var next = parent[i+1]; | |
parent[i] = next; | |
parent[i+1] = sel.obj; | |
} | |
function Register(raw){ | |
var history = $('history'); | |
var history_entry = CE('div'); | |
history_entry.contentEditable=false; | |
var form = CE('form'); | |
var input = CE('input'); | |
input.type = 'button'; | |
input.value = 'revive'; | |
input.setAttribute('onclick','Revive(event)'); | |
form.appendChild(input); | |
var hidden = CE('input'); | |
hidden.type = 'hidden'; | |
hidden.name = 'raw_exp'; | |
hidden.value = raw; | |
form.appendChild(hidden); | |
var span = CE('span'); | |
span.innerHTML = EncloseMath(JsonToMathML(JsonParser(raw))); | |
form.appendChild(span); | |
history_entry.appendChild(form); | |
history.appendChild(history_entry); | |
/* | |
<div> | |
<form> | |
<input type="button" value="revive" onclick="Revive(event);"> | |
<input type="hidden" name="raw_exp" value="['+', 1, 2]"> | |
<span> | |
<math xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mi> | |
ℒ | |
</mi> | |
</math> | |
<span> | |
</form> | |
</div> | |
*/ | |
} | |
function Revive(e){ | |
var target = e.target; | |
var from = target.form; | |
var input = $('input'); | |
input.value = from.elements.namedItem('raw_exp').value; | |
} | |
function Export(){ | |
var forms = $('history').getElementsByTagName('form'); | |
var builder = []; | |
builder.push('['); | |
for(var i = 0; i < forms.length; i++){ | |
builder.push("'"); | |
builder.push(encodeURI(forms.item(i).elements.namedItem('raw_exp').value)); | |
builder.push("'"); | |
builder.push(','); | |
} | |
builder.push(']'); | |
prompt("Export", builder.join("")); | |
} | |
function Inport(){ | |
var input = $('input').value; | |
var array = eval(input); | |
for(var i=0; i < array.length; i++){ | |
Register(decodeURI(array[i])); | |
} | |
} | |
function assertMatch(expectPat, actual) { | |
if(!actual.match(expectPat)) { | |
assertFail("pat not match. pat[" + expectPat + "], actual [" + actual + "]"); | |
} | |
} | |
function verify(input, expectPat, needLog) { | |
var sexp = JsonParser(input); | |
var result = JsonToMathML(sexp); | |
var actual_pat = expectPat; | |
if(needLog) | |
dp(result); | |
assertMatch(actual_pat, result); | |
} | |
function EvalTest() { | |
verify('["pow", "e", "a"]', /<msup><mrow><mi[^>]*>e<\/mi><\/mrow><mrow><mi[^>]*>a<\/mi><\/mrow><\/msup>/); | |
verify('["pow", "e", ["*", "a", "b"]]', /<msup><mrow><mi[^>]*>e<\/mi><\/mrow><mrow><mi[^>]*>a<\/mi><mi id="[0-9]*">b<\/mi><\/mrow><\/msup>/); | |
verify('["*", "e", ["-", "a"]]', /<mi id="[0-9]*">e<\/mi><mfenced id="[0-9]*"><mrow><mo>-<\/mo><mi id="[0-9]*">a<\/mi><\/mrow>/) | |
verify('["+", "λ", "a"]', /<mi id="[0-9]*">λ<\/mi><mo>\+<\/mo><mi id="[0-9]*">a<\/mi>/); | |
verify('["dotover", "x"]', /<mover><mi id="[0-9]*">x<\/mi><mo>.<\/mo><\/mover>/); | |
verify('["int", ["+", "x", "y"], "t"]', /<mo id="[0-9]*">∫<\/mo><mi id="[0-9]*">x<\/mi><mo>\+<\/mo><mi id="[0-9]*">y<\/mi><mi id="[0-9]*">dt<\/mi>/); | |
verify('["int", "x", "t"]', /<mo id="[0-9]*">∫<\/mo><mi id="[0-9]*">x<\/mi><mi id="[0-9]*">dt<\/mi>/); | |
verify('["int", "x", "t", 0, "∞"]', /<msubsup><mo id="[0-9]*">∫<\/mo><mn id="[0-9]*">0<\/mn><mi id="[0-9]*">∞<\/mi><\/msubsup><mi id="[0-9]*">x<\/mi><mi id="[0-9]*">dt<\/mi>/); | |
verify('["=", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">=<\/mo><mi id="[0-9]*">y<\/mi>/); | |
verify('["=", "x", ["+", "y", "z"]]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">=<\/mo><mi id="[0-9]*">y<\/mi><mo>\+<\/mo><mi id="[0-9]*">z<\/mi>/); | |
} | |
function verifySerializer(input, expect) { | |
var actual = SexpSerializer(JsonParser(input)); | |
assertEq(expect, actual); | |
} | |
function SerializerTest() { | |
verifySerializer(["a", 1, 2],'["a",1,2]'); | |
verifySerializer(["a", [1, 2]],'["a",[1,2]]'); | |
} | |
function UnitTest() { | |
try { | |
EvalTest(); | |
SerializerTest(); | |
dp("test sucess!"); | |
}catch(e) { | |
dp("test fail!"); | |
dp(e); | |
} | |
} | |
var KEY_ENTER = 13; | |
var KEY_LEFT = 37; | |
var KEY_RIGHT = 39; | |
var g_selected = undefined; | |
function onload() { | |
UnitTest(); | |
var canv = $("mathcanvas"); | |
canv.addEventListener("click", function(evt) { | |
if(g_selected) { | |
g_selected.elem.setAttribute("class", ""); | |
} | |
var id = evt.target.getAttribute("id"); | |
var obj = g_hash[id]; | |
if(!obj) { | |
g_selected = undefined; | |
return; | |
} | |
if(obj.parent && obj.parent[0] == "-") | |
obj = obj.parent; | |
g_selected = {obj: obj, elem: evt.target}; | |
g_selected.elem.setAttribute("class", "selectedTarget"); | |
// alert(g_selected.elem); | |
}); | |
document.body.addEventListener("keypress", function(evt) { | |
if(!g_selected) | |
return; | |
if(evt.keyCode == KEY_LEFT) { | |
MoveLeftTerm(g_selected); | |
Update(g_sexp); | |
} else if (evt.keyCode == KEY_RIGHT) { | |
MoveRightTerm(g_selected); | |
Update(g_sexp); | |
} else if (evt.keyCode == KEY_ENTER){ | |
Register($("input").value); | |
} | |
// alert(evt.keyCode); | |
}); | |
// alert(document.getElementById("hoge")); | |
//document.getElementById("hoge").addEventListener("click", function() { | |
// alert("clicked!"); } ); | |
/* | |
document.getElementById("x").addEventListener("click", function() { | |
alert("x!"); } ); | |
document.getElementById("d").addEventListener("click", function() { | |
alert("d!"); } ); | |
*/ | |
} | |
</script> | |
<br> | |
<textarea id="input" rows="2" cols="100" ></textarea><br> | |
<input value="Set" type="button" onclick="clicked();"> | |
<input value="Export" type="button" onclick="Export();"> | |
<input value="Import" type="button" onclick="Inport();"> | |
<br> | |
<div id="mathcanvas"></div> | |
<hr> | |
<input value="Clear" type="button" onclick="onClear();"><br> | |
<textarea id="console" rows="10" cols="100" ></textarea><br> | |
以下は適当な例。<br> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mrow class="selectedTarget"> | |
<mi>a</mi> | |
<mi>b</mi> | |
<mi>c</mi> | |
</mrow> | |
</math> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mi> | |
ℒ | |
</mi> | |
</math> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<msubsup> | |
<mo>∫</mo> | |
<mi>a</mi> | |
<mi>∞</mi> | |
</msubsup> | |
<mi>x</mi> | |
<mi>dt</mi> | |
</math> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mo>∫</mo> | |
<mi>x</mi> | |
<mi>dt</mi> | |
</math> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mover> | |
<mi>r</mi> | |
<mo>.</mo> | |
</mover> | |
</math> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mo>λ</mo> | |
</math> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<msup> | |
<mrow> | |
<msub> | |
<mrow> | |
<mi> e </mi> | |
</mrow> | |
<mrow> | |
<mi>a</mi> | |
</mrow> | |
</msub> | |
</mrow> | |
<mrow> | |
<mo> - </mo> | |
<mi> ρ<!--greek small letter rho--> </mi> | |
<mi> x </mi> | |
</mrow> | |
</msup> | |
</math> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<msubsup> | |
<mrow> | |
<mi> e </mi> | |
</mrow> | |
<mrow> | |
<mi>a</mi> | |
</mrow> | |
<mrow> | |
<mo> - </mo> | |
<mi> ρ<!--greek small letter rho--> </mi> | |
<mi> x </mi> | |
</mrow> | |
</msubsup> | |
</math> | |
<br> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mn> 3 </mn> | |
<mo> + </mo> | |
<mn> 4 </mn> | |
</math> | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mrow id="hoge"> | |
<munderover> | |
<mo>∫</mo> | |
<mn>0</mn> | |
<mn>1</mn> | |
</munderover> | |
<mrow> | |
<msup> | |
<mi>e</mi> | |
<mi id="x">x</mi> | |
</msup> | |
<mi id="d">d</mi> | |
<mi>x</mi> | |
</mrow> | |
</mrow> | |
</math> | |
<math xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mfenced> | |
<mrow> | |
<mi> a </mi> | |
<mo> + </mo> | |
<mi> b </mi> | |
</mrow> | |
</mfenced> | |
</math> | |
以下はダメ | |
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML"> | |
<mrow> | |
<cn type="complex-cartesian"> 12.3 <sep/> 5 </cn> | |
<bind id="outer"><csymbol cd="fns1">lambda</csymbol> | |
<bvar><ci>x</ci></bvar> | |
<apply><ci>f</ci> | |
<bind id="inner"><csymbol cd="fns1">lambda</csymbol> | |
<bvar><ci>x</ci></bvar> | |
<share id="copy" href="#orig"/> | |
</bind> | |
<apply id="orig"><ci>g</ci><ci>x</ci></apply> | |
</apply> | |
</bind> | |
</mrow> | |
</math> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment