Skip to content

Instantly share code, notes, and snippets.

@karino2
Forked from hogeika/test.html
Created March 1, 2012 18:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save karino2/1951892 to your computer and use it in GitHub Desktop.
Save karino2/1951892 to your computer and use it in GitHub Desktop.
EquationPad
<html>
<head>
<style type="text/css">
.selectedTarget { background-color: red}
</style>
</head>
<body onload="onload()">
<input value="Export" type="button" onclick="Export();">
<input value="Import" type="button" onclick="Import();">
<input type="file" id="load_file">
<input value="Load" type="button" onclick="Load()">
<input value="Save" type="button" onclick="Save()">
<input id="import_data_hidden" type="hidden"
value="[['%5B%22=%22,%0A%20%20%22Q%22,%0A%20%20%5B%22pow%22,%0A%20%20%20%20%5B%22*%22,%22K%22,%22L%22%5D,%0A%20%20%20%20%5B%22/%22,%221%22,%22a%22%5D%0A%20%20%5D%0A%5D','%22Q%E3%81%AF%E7%94%A3%E5%87%BA%E9%87%8F%E3%80%81K%E3%81%AF%E8%B3%87%E6%9C%AC%E3%81%A7%E7%9F%AD%E6%9C%9F%E3%81%A7%E3%81%AF%E5%9B%BA%E5%AE%9A%E3%80%81L%E3%81%AF%E5%8A%B4%E5%83%8D%22',],[]]">
<div id="history" contenteditable="true"></div>
<script language="javascript">
var g_DEFAULT_IMPORT = "";
var g_VERVOSE = false;
function assertFail(msg) {
if(g_VERVOSE)
alert("fail! "+ msg);
throw msg;
}
function assertFalse(condition, msg) {
if(condition)
assertFail("assert false fail:" + 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 assertNStrictEq(exp, act) {
if(exp === act)
assertFail("not equal [" + exp + "] and [" + act + "]");
}
function $(id) {
return document.getElementById(id);
}
function CE(tag){
return document.createElement(tag);
}
function QueryInput(title, initVal) {
return prompt(title, initVal);
}
/*
------------------------------------------------------------------------------
utility DONE, parser Start
------------------------------------------------------------------------------
*/
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 "Unknown 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, id) {
if(id)
builder.push('<mo id="' + id + '">');
else
builder.push("<mo>");
builder.push(tagName);
builder.push("</mo>");
}
function PushMi(builder, name, id) {
if(id)
builder.push('<mi id="' + id + '">');
else
builder.push("<mi>");
builder.push(name);
builder.push("</mi>");
}
function IsMinus(term) {
return term[0] == "-";
}
function IsSingleTerm(term) {
return !(term[0] == "+");
}
function PushMinusTerm(builder, term) {
assertEq(true, IsMinus(term));
PushMrow(builder, term.objId);
PushMo(builder, "-", term[0].objId);
EvalTerm(builder,term[1], IsSingleTerm(term[1]));
builder.push('</mrow>');
}
function FirstHeadIsNotMinus(sexp) {
if(sexp[0] == "-")
return false;
if(sexp[0] != "*")
return true;
return FirstHeadIsNotMinus(sexp[1]);
}
function EvalPlus(json) {
var builder = [];
var len = json.length;
PushMrow(builder, json.objId);
if(IsMinus(json[1])){
PushMinusTerm(builder, json[1]);
}
else
EvalTerm(builder, json[1], IsSingleTerm(json[1]));
for(var i = 2; i < len; i++) {
if(IsMinus(json[i])){
PushMinusTerm(builder, json[i]);
}
else {
PushMo(builder, "+");
EvalTerm(builder,json[i], IsSingleTerm(json[i]) && FirstHeadIsNotMinus(json[i]));
}
}
builder.push('</mrow>');
return builder.join("");
}
function PushMrow(builder, id) {
builder.push('<mrow id="'+ id + '">');
}
function EvalTimes(json) {
var builder = [];
var len = json.length;
PushMrow(builder, json.objId);
EvalTerm(builder, json[1], IsSingleTerm(json[1]));
for(var i = 2; i < len; i++) {
if(json[i-1] instanceof Number && json[i] instanceof Number)
PushMo(builder, "&times;");
EvalTerm(builder,json[i], IsSingleTerm(json[i]) && !IsMinus(json[i]));
}
builder.push('</mrow>');
return builder.join("");
}
function EvalEquation(json, strOp) {
var builder = [];
PushMrow(builder, json.objId);
EvalTerm(builder, json[1], true);
PushMo(builder, strOp, json[0].objId);
EvalTerm(builder,json[2], true);
builder.push("</mrow>");
return builder.join("");
}
function EvalPower(json) {
var builder = [];
builder.push('<msup id="' + json.objId + '">');
EvalTerm(builder, json[1]);
EvalTerm(builder, json[2], true);
builder.push("</msup>");
return builder.join("");
}
function EvalSubIndex(json) {
var builder = [];
builder.push('<msub id="' + json.objId + '">');
EvalTerm(builder, json[1], true);
EvalTerm(builder, json[2], true);
builder.push("</msub>");
return builder.join("");
}
function EvalFunction(json) {
var builder = [];
PushMrow(builder, json.objId);
EvalTerm(builder,json[1], true);
builder.push("<mfenced>");
EvalTerm(builder,json[2], true);
builder.push("</mfenced>");
builder.push('</mrow>');
return builder.join("");
}
function EvalComma(json) {
var builder = [];
for(var i = 1; i < json.length; i++) {
EvalTerm(builder,json[i], true);
}
return builder.join("");
}
function EvalPartial(json) {
var builder = [];
builder.push('<mfrac id="' +json.objId + '">');
builder.push("<mrow>");
PushMo(builder, "&part;");
EvalTerm(builder, json[1]);
builder.push("</mrow>");
builder.push("<mrow>");
PushMo(builder, "&part;");
EvalTerm(builder, json[2]);
builder.push("</mrow>");
builder.push("</mfrac>");
return builder.join("");
}
function EvalFrac(json) {
var builder = [];
builder.push('<mfrac id="' +json.objId + '"><mrow>');
EvalTerm(builder, json[1], true);
builder.push('</mrow><mrow>');
EvalTerm(builder, json[2], true);
builder.push('</mrow></mfrac>');
return builder.join("");
}
function EvalDotOver(json) {
var builder = [];
builder.push('<mover id="' + json.objId + '">');
EvalTerm(builder, json[1]);
builder.push("<mo>.</mo>");
builder.push("</mover>");
return builder.join("");
}
function EvalIntegral(json) {
var builder = [];
PushMrow(builder, json.objId);
if(json.length == 3) {
PushMo(builder, "&Integral;", json[0].objId);
EvalTerm(builder, json[1], true);
// currently, json[2] does'nt support &rho;, etc.
builder.push(Mi(json[2].objId, "d" + json[2] ));
} else if(json.length == 5) {
builder.push("<msubsup>");
builder.push(Mo(json[0].objId, "&Integral;"));
EvalTerm(builder, json[3], true);
EvalTerm(builder, json[4], true);
builder.push("</msubsup>");
EvalTerm(builder, json[1], true);
// currently, json[2] does'nt support &rho;, etc.
builder.push(Mi(json[2].objId, "d" + json[2] ));
} else {
throw "unknown arg number of integral. ( " + json.length + ")";
}
builder.push("</mrow>");
return builder.join("");
}
function EvalMaxMin(json, maxmin) {
var builder = [];
PushMrow(builder, json.objId);
builder.push("<munder>");
PushMi(builder, maxmin, json[0].objId);
EvalTerm(builder, json[2], true);
builder.push("</munder>");
builder.push('<mfenced open="{" close="}">');
EvalTerm(builder, json[1], true);
builder.push('</mfenced>');
builder.push("</mrow>");
return builder.join("");
}
function EvalMax(json) {
return EvalMaxMin(json, "max");
}
function EvalMin(json) {
return EvalMaxMin(json, "min");
}
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 == "_")
{
return EvalSubIndex(json);
}
if(tag == "part")
{
return EvalPartial(json);
}
if(tag == "fun")
{
return EvalFunction(json);
}
if(tag == "/")
{
return EvalFrac(json);
}
if(tag == "dotover")
{
return EvalDotOver(json);
}
if(tag == "int")
{
return EvalIntegral(json);
}
if(tag == "max")
{
return EvalMax(json);
}
if(tag == "min")
{
return EvalMin(json);
}
if(tag == "=")
{
return EvalEquation(json, "=");
}
if(tag == "<")
{
return EvalEquation(json, "&lt;");
}
if(tag == "<=")
{
return EvalEquation(json, "&le;");
}
if(tag == ">")
{
return EvalEquation(json, "&gt;");
}
if(tag == ">=")
{
return EvalEquation(json, "&ge;");
}
if(tag == ",")
{
return EvalComma(json);
}
if((json instanceof String) || (json instanceof Number))
{
var builder = [];
EvalTerm(builder, json);
return builder.join("");
}
if(json.length == 1)
{
var builder = [];
EvalTerm(builder, json[0]);
return builder.join("");
}
throw "unknown tag:" + tag;
}
var g_hash = {};
function RegisterObjectToHash(obj) {
obj.objId = IdGen();
g_hash[obj.objId] = obj;
}
function ParseSingleItem(item) {
var obj;
if(typeof(item) == "number")
obj = new Number(item);
else if(typeof(item) == "string")
obj = new String(item);
else if(item instanceof Array)
obj = JsonParser(item);
else
obj = item;
RegisterObjectToHash(obj);
return obj;
}
function JsonParser(str) {
var sexp = eval(str);
var res = [];
res.objId = IdGen();
g_hash[res.objId] = res;
if(sexp instanceof Array) {
for(var i = 0; i < sexp.length; i++) {
var obj = ParseSingleItem(sexp[i]);
res.push(obj);
obj.parent = res;
}
} else {
var obj = ParseSingleItem(sexp);
res = obj; // return atom, not array!
}
assertNStrictEq(res, sexp);
return res;
}
/*
-----------------------------------------------------------------------------
HTML element related
-----------------------------------------------------------------------------
*/
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 = '';
}
function ClearMathCanvas(){
var input = $('input');
input.value = "";
var canvas = $('mathcanvas');
canvas.innerHTML = "";
}
/*
----------------------------------------------------------------------------
HTML related done. Update related start.
----------------------------------------------------------------------------
*/
var g_sexp;
function UpdateInputToCanvas() {
g_selected = undefined;
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 Update(sexp) {
var result = JsonToMathML(sexp);
dp(result);
showMath(result);
UpdateSelection();
$("input").value = SexpSerializer(sexp);
}
/*
----------------------------------------------------------------
Update related done. Prity Print related start.
----------------------------------------------------------------
*/
function HasArrayChild(sexp) {
for(var i = 0; i < sexp.length; i++) {
if(sexp[i].length > 1) {
return true;
}
}
return false;
}
function PushIndent(builder, level) {
for(var i = 0; i < level; i++) {
builder.push(" ");
}
}
function InnerSexpSerializer(sexp, builder, level) {
if(sexp instanceof Number) {
PushIndent(builder, level);
builder.push(sexp.toString());
return;
}
if(sexp instanceof String) {
PushIndent(builder, level);
builder.push('"');
builder.push(sexp);
builder.push('"');
return;
}
if(sexp instanceof Array) {
PushIndent(builder, level);
builder.push("[");
assertFalse(sexp[0] instanceof Array, "head of array must not array.");
InnerSexpSerializer(sexp[0], builder, 0);
if(HasArrayChild(sexp)) {
for(var i = 1; i < sexp.length; i++) {
builder.push(",\n");
InnerSexpSerializer(sexp[i], builder, level+1);
}
builder.push("\n");
PushIndent(builder, level);
builder.push("]");
}
else {
for(var i = 1; i < sexp.length; i++) {
builder.push(",");
InnerSexpSerializer(sexp[i], builder, 0);
}
builder.push("]");
}
return;
}
alert(typeof(sexp));
throw "unknown atom type inside S Expression Serializer";
}
function SexpSerializer(sexp) {
var builder = [];
var level = 0;
InnerSexpSerializer(sexp, builder, level);
return builder.join("");
}
/*
----------------------------------------------------------------
Prity Print related done.
----------------------------------------------------------------
*/
/*
----------------------------------------------------------------
Command related start.
----------------------------------------------------------------
*/
// -------- CutBuffer related Command. ---------------------
function CopyRawExpression(raw) {
var par = $('cutbuffer');
var entry = CE("li");
SaveExpression(entry, raw);
if(par.firstChild) {
par.insertBefore(entry, par.firstChild);
} else {
par.appendChild(entry);
}
}
function CutBufferMoveForward() {
var cutbuf = $('cutbuffer');
var li = cutbuf.firstChild;
// move to last
if(li)
cutbuf.appendChild(li);
}
function CutBufferMoveBackword() {
var cutbuf = $('cutbuffer');
var li = cutbuf.lastChild;
// move to first
if(li)
cutbuf.insertBefore(li, cutbuf.firstChild);
}
function CutBufferRotate(isBackward) {
if(isBackward) {
CutBufferMoveBackword();
}else{
CutBufferMoveForward();
}
}
function Copy(sexp) {
var raw = SexpSerializer(sexp);
CopyRawExpression(raw);
}
function MoveUpExpression(targetExp) {
var parent = targetExp.parent;
if(!parent.parent)
g_sexp = targetExp;
else {
Replace(parent.parent, parent, targetExp);
}
g_selected = {obj: targetExp};
}
function Cut(sexp) {
Copy(sexp);
var parent = sexp.parent;
if(!parent)
return ;
if((parent[0] != "+") && (parent[0] != "*") && (parent[0] != ","))
return;
if(g_selected.obj = sexp)
g_selected = undefined;
var i = FindIndex(parent, sexp);
parent.splice(i, 1);
if(parent[0] == ",")
return;
// only one term remains. go up!
if(parent.length == 2) {
var single = parent[1];
MoveUpExpression(single);
}
}
// ---------------- Replace Expression related ---------
function ReplaceExpression(targetSexp, newExp) {
if(g_selected && g_selected.obj == targetSexp)
g_selected = {obj: newExp}; // caution! no elem until UpdateSelection!
if(!targetSexp.parent) {
// targetSexp is single term. so just replace g_sexp
g_sexp = newExp;
return;
}
var parent = targetSexp.parent;
Replace(parent, targetSexp, newExp);
return newExp
}
// orgExp is already inside newSexp.
function ReplaceWithWrappedExpression(newSexp, orgExp) {
// use sel.obj.parent inside ReplaceExpression. So update after.
ReplaceExpression(orgExp, newSexp);
orgExp.parent = newSexp;
}
// --- replace expression's command below ---
function ReplaceWithFirstQueryLast(sexp, raw) {
ReplaceWithNthQueryLast(sexp, raw, 1);
}
function ReplaceWithFirst(sexp, raw) {
ReplaceWithNth(sexp, raw, 1);
}
function ReplaceWithNthQueryLast(sexp, raw, nth) {
var inputStr = QueryInput("Symbol", "");
if(inputStr) {
var newSexp = JsonParser(raw);
var atom = ParseInputToTerm(inputStr);
Replace(newSexp, newSexp[newSexp.length-1], atom);
newSexp[nth] = sexp;
ReplaceWithWrappedExpression(newSexp, sexp);
g_selected = {obj: atom};
}
}
function ReplaceWithNth(sexp, raw, nth) {
var newSexp = JsonParser(raw);
newSexp[nth] = sexp;
ReplaceWithWrappedExpression(newSexp, sexp);
}
function AddArgumentAt(selObj, argIndex) {
var inputStr = QueryInput("New symbol", "");
if(!inputStr)
return;
var atom = ParseInputToTerm(inputStr);
if(selObj[argIndex] instanceof Array &&
selObj[argIndex][0] == ",") {
atom.parent = selObj[argIndex];
selObj[argIndex].push(atom);
} else {
var newSexp = JsonParser('[",", "dummy", "x"]');
Replace(newSexp, newSexp[newSexp.length-1], atom);
newSexp.parent = selObj;
selObj[argIndex].parent = newSexp;
newSexp[1] = selObj[argIndex];
selObj[argIndex] = newSexp;
}
}
function AddArgument(sel) {
var selObj = sel.obj;
if(selObj[0] != "fun" && selObj[0] != "max" && selObj[0] != "min") {
alert("argument cannot add to " + selObj[0]);
return;
}
var argIndex;
if(selObj[0] == "fun") {
argIndex = 2;
} else {
argIndex = 1;
}
return AddArgumentAt(selObj, argIndex);
}
function Minus(sel) {
if(sel.obj[0] == "-") {
var selected_obj = sel.obj;
var par = selected_obj.parent;
selected_obj[1].parent = par;
Replace(par, selected_obj, selected_obj[1]);
if(g_selected.obj == selected_obj) {
g_selected = {obj: selected_obj[1]}; // caution! no elem until UpdateSelection!
}
} else {
var newSexp = JsonParser('["-", "dummy"]');
newSexp[1] = sel.obj;
ReplaceWithWrappedExpression(newSexp, sel.obj);
}
}
function SwitchSelection(sexp) {
if(sexp[0] == "=") {
swap(sexp, 1, 2);
return;
} else {
FracUpsideDown(g_selected.obj);
}
}
function FracUpsideDown(sexp) {
if(sexp[0] != "/") {
ReplaceWithNth(sexp, '["/", "1", "dummy"]', 2);
return;
}
swap(sexp, 1, 2);
if(sexp[2] == 1) {
MoveUpExpression(sexp[1]);
return;
}
}
function Flatten(sel) {
var sexp = sel.obj;
var tag = sexp[0];
var parent = sexp.parent;
if(parent[0].toString() != tag.toString()) {
alert("parent and selected region not the same tag: " + tag + " & " + parent[0]);
return;
}
var index = FindIndex(parent, sexp);
var args = [index, 1];
for(var i = 1; i < sexp.length; i++) {
sexp[i].parent = parent;
args.push(sexp[i]);
}
parent.splice.apply(parent, args);
g_selected = undefined;
}
function ParseInputToTerm(inputStr) {
var num = parseFloat(inputStr);
var atom;
if(!isNaN(num))
{
atom = new Number(num);
} else {
atom = new String(inputStr);
}
RegisterObjectToHash(atom);
return atom;
}
function ReplaceTermByInput(inputStr) {
var atom = ParseInputToTerm(inputStr);
var selObj = g_selected.obj;
Replace(selObj.parent, selObj, atom);
g_selected = {obj: atom };
return;
}
// ------------------------ Replace related done ----------------------------
function Paste(targetSexp) {
var raw = HeadRaw();
if(raw) {
if(targetSexp.parent){
UnSelectExpression(true);
var newCell = ReplaceExpression(targetSexp, JsonParser(raw));
} else {
g_selected = undefined;
$('input').value = raw;
g_sexp = JsonParser($("input").value);
}
}
}
// ---------------- Move Terminal related ---------------------
function FindIndex(arr, obj) {
for(var i = 0; i < arr.length; i++) {
if(arr[i] == obj)
return i;
}
return -1;
}
function Replace(parent, target, newTarget) {
if(!parent) {
g_sexp = newTarget;
g_selected = undefined;
return;
}
var i = FindIndex(parent, target);
parent[i] = newTarget;
newTarget.parent = parent;
}
function swap(array, i, j) {
var iobj = array[i];
array[i] = array[j];
array[j] = iobj;
}
function MoveLeftTermInternal(parent_sexp, target_sexp) {
var i = FindIndex(parent_sexp, target_sexp);
if(i == 1)
return;
assertEq(parent_sexp[i], target_sexp);
swap(parent_sexp, i, i-1);
}
function MoveLeftTerm(sel) {
if(!sel.obj.parent)
return;
MoveLeftTermInternal(sel.obj.parent, sel.obj);
}
function MoveRightTermInternal(parent_sexp, target_sexp) {
var i = FindIndex(parent_sexp, target_sexp);
if(i == parent_sexp.length -1 )
return;
assertEq(parent_sexp[i], target_sexp);
swap(parent_sexp, i, i+1);
}
function MoveRightTerm(sel) {
if(!sel.obj.parent)
return;
MoveRightTermInternal(sel.obj.parent, sel.obj);
}
// ------------------------ history related ------------------------
function SaveExpression(entry, raw) {
var hidden = CE('input');
hidden.type = 'hidden';
hidden.name = 'raw_exp';
hidden.value = raw;
entry.appendChild(hidden);
var span = CE('span');
span.innerHTML = EncloseMath(JsonToMathML(JsonParser(raw)));
entry.appendChild(span);
}
function Register(raw){
var history = $('history');
var history_entry = CE('div');
history_entry.contentEditable=false;
history_entry.setAttribute('style', "background-color:Aqua; word-wrap:normal;");
var form = CE('form');
form.innerHTML='<input type="button" value="O" onclick="OverwriteClicked(event.target)">';
var input = CE('input');
input.type = 'button';
input.value = 'revive';
input.setAttribute('onclick','Revive(event)');
form.appendChild(input);
SaveExpression(form, raw);
history_entry.appendChild(form);
history.appendChild(history_entry);
history.appendChild(CE('br'));
// should I return history_entry?
return form;
}
function GetHistoryForms() {
var history = $('history');
return history.getElementsByTagName("form");
}
function HeadRaw() {
var cutbuf = $('cutbuffer');
var li = cutbuf.firstChild
if(li) {
var inp = li.getElementsByTagName("input")[0];
return inp.value;
}
return undefined;
}
function OverwriteClicked(buttonElem){
var raw = $('input').value;
var form = buttonElem.parentNode;
form.elements.namedItem('raw_exp').value = raw;
form.getElementsByTagName('span')[0].innerHTML = EncloseMath(JsonToMathML(JsonParser(raw)));
}
function ReviveForm(form) {
var input = $('input');
input.value = form.elements.namedItem('raw_exp').value;
UpdateInputToCanvas();
}
function Revive(e){
var target = e.target;
var form = target.form;
ReviveForm(form);
}
// ------------------------Export, Import, Save, Load ----------------------------
function Export(){
var forms = GetHistoryForms();
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('],[');
var cutbufEnts = $('cutbuffer').getElementsByTagName("li");
for(var i = 0; i < cutbufEnts.length; i++) {
builder.push("'");
builder.push(encodeURI(cutbufEnts.item(i).getElementsByTagName('input')[0].value));
builder.push("'");
builder.push(',');
}
builder.push(']]');
prompt("Export", builder.join(""));
}
function Import(){
var input = QueryInput("Import", $('import_data_hidden').value);
if(!input)
return;
var array = eval(input);
for(var i=0; i < array[0].length; i++){
Register(decodeURI(array[0][i]));
}
for(var i = 0;i < array[1].length; i++) {
var j = array[1].length -i-1;
CopyRawExpression(decodeURI(array[1][j]));
}
}
function Save(){
ClearMathCanvas();
var console = $('console');
console.value = "<html><head>\n" + document.head.innerHTML + "</head><body onload=\"onload()\">\n" + document.body.innerHTML + "</body></html>";
}
function FrozenedContent(){
var console = $('console');
console.value ='';
var his = $('history');
var hidden = $('import_data_hidden');
var maths = his.getElementsByTagName("math");
var div = CE('div');
div.appendChild(hidden.cloneNode(true));
var ul = CE('ul');
for(var i = 0; i < maths.length; i++) {
var li = CE('li');
li.appendChild(maths.item(i).cloneNode(true));
ul.appendChild(li);
}
div.appendChild(ul);
console.value = div.innerHTML;
}
function GetDocumentFromString(str){
var iframe = $('iframe');
var tmpDoc = iframe.contentDocument;
tmpDoc.open();
tmpDoc.write(str);
tmpDoc.close();
return tmpDoc;
}
function Load(){
var file = $('load_file').files[0];
if(file){
var reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = function (){
var doc = GetDocumentFromString(reader.result);
var history = doc.getElementById('history');
var inputs = history.getElementsByTagName('input');
for(var i = 0; i < inputs.length; i++){
var input = inputs.item(i);
if(input.type == 'hidden' && input.name == 'raw_exp' && input.value){
Register(input.value);
}
}
var cutbuffer = doc.getElementById('cutbuffer');
var inputs = cutbuffer.getElementsByTagName('input');
for(var i = inputs.length - 1; i >= 0; i --){
var input = inputs.item(i);
if(input.type == 'hidden' && input.name == 'raw_exp' && input.value){
CopyRawExpression(input.value);
}
}
};
}
}
/*
---------------------------------------------------------
Selection related
---------------------------------------------------------
*/
var g_selected = undefined;
function UnSelectExpression(notClear) {
if(g_selected && g_selected.elem) {
g_selected.elem.setAttribute("class", "");
}
if(!notClear)
g_selected = undefined;
return false;
}
function WidenSelection(sel) {
var par = sel.obj.parent;
if(!par)
return;
if(!par.objId)
return;
var par_elem = $(par.objId);
if(!par_elem)
return;
g_selected = {obj: par, elem: par_elem };
}
function SelectAll() {
var root = $(g_sexp.objId);
g_selected = {obj: g_sexp, elem: root};
}
/*
---------------------------------------------------------
DOM Event Handler related
---------------------------------------------------------
*/
var KEY_ENTER = 13;
var KEY_LEFT = 37;
var KEY_RIGHT = 39;
var KEY_A = 65;
var KEY_C = 67;
var KEY_D = 68;
var KEY_E = 69;
var KEY_F = 70;
var KEY_G = 71;
var KEY_I = 73;
var KEY_J = 74;
var KEY_L = 76;
var KEY_M = 77;
var KEY_O = 79;
var KEY_P = 80;
var KEY_R = 82;
var KEY_S = 83;
var KEY_T = 84;
var KEY_V = 86;
var KEY_W = 87;
var KEY_X = 88;
function BodyOnKeyUp(evt) {
if(g_inputFocus)
return;
if(evt.keyCode == KEY_A) {
SelectAll();
UpdateSelection();
return;
}
if(evt.keyCode == KEY_I &&
evt.shiftKey) {
Import();
return;
}
if(evt.keyCode == KEY_E &&
evt.shiftKey) {
Export();
return;
}
if(evt.keyCode == KEY_P){
CutBufferRotate(evt.shiftKey);
return;
}
if(evt.keyCode == KEY_L){
ReplaceWithFirstQueryLast(g_selected.obj, '["<=", "dummy", "x"]');
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_G){
ReplaceWithFirstQueryLast(g_selected.obj, '[">=", "dummy", "x"]');
Update(g_sexp);
return;
}
if(!g_selected) {
if(evt.keyCode == KEY_C) {
CopyRawExpression($('input').value);
return;
}
}
if(!g_selected)
return;
if(evt.keyCode == KEY_D &&
evt.shiftKey) {
ReplaceWithFirstQueryLast(g_selected.obj, '["part", "dummy", "t"]');
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_M) {
if(evt.shiftKey) {
ReplaceWithFirstQueryLast(g_selected.obj, '["min", "dummy", "x"]');
} else {
ReplaceWithFirstQueryLast(g_selected.obj, '["max", "dummy", "x"]');
}
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_I && !evt.shiftKey) {
ReplaceWithFirstQueryLast(g_selected.obj, '["int", "dummy", "t", 0, "x"]');
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_E && !evt.shiftKey) {
Flatten(g_selected);
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_F) {
ReplaceWithFirstQueryLast(g_selected.obj, '["fun", "dummy", "x"]');
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_T) {
ReplaceWithFirst(g_selected.obj, '["dotover", "dummy"]');
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_W) {
WidenSelection(g_selected);
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_C) {
Copy(g_selected.obj);
return;
}
if(evt.keyCode == KEY_R) {
var inputStr = QueryInput("Replace?", "");
if(inputStr) {
ReplaceTermByInput(inputStr);
Update(g_sexp);
}
return;
}
if(evt.keyCode == KEY_S) {
SwitchSelection(g_selected.obj);
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_X) {
Cut(g_selected.obj);
Update(g_sexp);
return;
}
if(evt.keyCode == KEY_V){
Paste(g_selected.obj);
Update(g_sexp);
return;
}
}
function BodyOnKeyPress(evt) {
if(g_inputFocus)
return;
if(evt.charCode == "=".charCodeAt(0)) {
ReplaceWithFirstQueryLast(g_selected.obj, '["=", "dummy", "x"]');
Update(g_sexp);
return;
}
if(evt.charCode == "<".charCodeAt(0)) {
ReplaceWithFirstQueryLast(g_selected.obj, '["<", "dummy", "x"]');
Update(g_sexp);
return;
}
if(evt.charCode == ">".charCodeAt(0)) {
ReplaceWithFirstQueryLast(g_selected.obj, '[">", "dummy", "x"]');
Update(g_sexp);
return;
}
if (!g_selected) {
if(evt.keyCode == KEY_ENTER){
Register($("input").value);
return;
} else if(evt.charCode == "+".charCodeAt(0)) {
$('input').value = '"x"';
UpdateInputToCanvas();
return;
}
}
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){
var raw = SexpSerializer(g_selected.obj);
var form = Register(raw);
ReviveForm(form);
} else if(evt.charCode == "+".charCodeAt(0)) {
ReplaceWithFirstQueryLast(g_selected.obj, '["+", "dummy", "y"]');
Update(g_sexp);
} else if(evt.charCode == "-".charCodeAt(0)) {
Minus(g_selected);
Update(g_sexp);
} else if(evt.charCode == "*".charCodeAt(0)) {
ReplaceWithFirstQueryLast(g_selected.obj, '["*", "dummy", "y"]');
Update(g_sexp);
} else if(evt.charCode == "/".charCodeAt(0)) {
ReplaceWithFirstQueryLast(g_selected.obj, '["/", "dummy", "y"]');
Update(g_sexp);
} else if(evt.charCode == "_".charCodeAt(0)) {
ReplaceWithFirstQueryLast(g_selected.obj, '["_", "dummy", "t"]');
Update(g_sexp);
} else if(evt.charCode == "^".charCodeAt(0)) {
ReplaceWithFirstQueryLast(g_selected.obj, '["pow", "dummy", "t"]');
Update(g_sexp);
} else if(evt.charCode == ",".charCodeAt(0)) {
AddArgument(g_selected);
Update(g_sexp);
}
// alert(evt.charCode);
// alert(evt.keyCode);
}
var g_inputFocus = false;
function OnInputFocus() {
g_inputFocus = true;
UnSelectExpression();
}
function OnInputBlur() {
g_inputFocus = false;
}
function OnInputKeyDown(evt) {
if(evt.keyCode == KEY_J && evt.ctrlKey)
{
evt.stopPropagation();
evt.preventDefault();
UpdateInputToCanvas();
return false;
}
return true;
}
function onload() {
UnitTest();
var canv = $("mathcanvas");
canv.addEventListener("click", function(evt) {
UnSelectExpression();
var id = evt.target.getAttribute("id");
var obj = g_hash[id];
if(!obj) {
return;
}
g_selected = {obj: obj, elem: evt.target};
g_selected.elem.setAttribute("class", "selectedTarget");
// alert(g_selected.elem);
});
document.body.addEventListener("keyup", BodyOnKeyUp);
document.body.addEventListener("keypress", BodyOnKeyPress);
}
/*
---------------------------------------------------------------
Unit Test realted start
---------------------------------------------------------------
*/
function assertMatch(expectPat, actual, msg) {
if(!actual.match(expectPat)) {
assertFail("pat not match. " + msg + ": 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, input);
}
function EvalTest() {
verify('["pow", "e", "a"]', /<msup id="[0-9]*"><mi[^>]*>e<\/mi><mi[^>]*>a<\/mi><\/msup>/);
verify('["pow", "e", ["*", "a", "b"]]', /<msup id="[0-9]*"><mi[^>]*>e<\/mi><mrow[^>]*><mi[^>]*>a<\/mi><mi id="[0-9]*">b<\/mi><\/mrow><\/msup>/);
verify('["*", "e", ["-", "a"]]', /<mrow id="[0-9]*"><mi id="[0-9]*">e<\/mi><mfenced id="[0-9]*"><mrow><mrow id="[0-9]*"><mo[^>]*>-<\/mo><mi id="[0-9]*">a<\/mi><\/mrow><\/mrow><\/mfenced><\/mrow>/);
//<mn id="84">3</mn><mo>+</mo><mrow id="86"><mi id="87">e</mi><mfenced id="91"><mrow><mrow id="89"><mo[^>]*>-</mo><mi id="90">a</mi></mrow></mrow></mfenced></mrow>
verify('["+", 3, ["*", "e", ["-", "a"]]]', /<mn id="[0-9]*">3<\/mn><mo>\+<\/mo><mrow id="[0-9]*"><mi id="[0-9]*">e<\/mi><mfenced id="[0-9]*"><mrow><mrow id="[0-9]*"><mo[^>]*>-<\/mo><mi id="[0-9]*">a<\/mi><\/mrow><\/mrow><\/mfenced><\/mrow>/);
// <mn id="106">3</mn><mo>+</mo><mfenced id="114"><mrow><mrow id="108"><mrow id="110"><mo[^>]*>-</mo><mi id="111">a</mi></mrow><mi id="113">e</mi></mrow></mrow></mfenced>
verify('["+",3,["*",["-","a"], "e"]]', /<mn id="[0-9]*">3<\/mn><mo>\+<\/mo><mfenced id="[0-9]*"><mrow><mrow id="[0-9]*"><mrow id="[0-9]*"><mo[^>]*>-<\/mo><mi id="[0-9]*">a<\/mi><\/mrow><mi id="[0-9]*">e<\/mi><\/mrow><\/mrow><\/mfenced>/);
verify('["+", "&lambda;", "a"]', /<mi id="[0-9]*">&lambda;<\/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]*">&Integral;<\/mo><mrow id="[0-9]*"><mi id="[0-9]*">x<\/mi><mo>\+<\/mo><mi id="[0-9]*">y<\/mi><\/mrow><mi id="[0-9]*">dt<\/mi>/);
verify('["int", "x", "t"]', /<mo id="[0-9]*">&Integral;<\/mo><mi id="[0-9]*">x<\/mi><mi id="[0-9]*">dt<\/mi>/);
verify('["int", "x", "t", 0, "&infin;"]', /<msubsup><mo id="[0-9]*">&Integral;<\/mo><mn id="[0-9]*">0<\/mn><mi id="[0-9]*">&infin;<\/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><mrow id="[0-9]*"><mi id="[0-9]*">y<\/mi><mo>\+<\/mo><mi id="[0-9]*">z<\/mi><\/mrow>/);
verify('["part", "y", "x"]', /<mfrac id="[0-9]*"><mrow><mo>&part;<\/mo><mi id="[0-9]*">y<\/mi><\/mrow><mrow><mo>&part;<\/mo><mi id="[0-9]*">x<\/mi><\/mrow><\/mfrac>/);
verify('["/", "x", "y"]', /<mfrac id="[0-9]*"><mrow><mi id="[0-9]*">x<\/mi><\/mrow><mrow><mi id="[0-9]*">y<\/mi><\/mrow><\/mfrac>/);
verify('["max", "y", "x"]', /<mrow[^>]*><munder[^>]*><mi[^>]*>max<\/mi><mi[^>]*>x<\/mi><\/munder><mfenced open="{" close="}"><mi[^>]*>y<\/mi><\/mfenced><\/mrow>/);
verify('["min", "y", "x"]', /<mrow[^>]*><munder[^>]*><mi[^>]*>min<\/mi><mi[^>]*>x<\/mi><\/munder><mfenced open="{" close="}"><mi[^>]*>y<\/mi><\/mfenced><\/mrow>/);
verify('["_", "x", "a"]', /<msub[^>]*><mi[^>]*>x<\/mi><mi[^>]*>a<\/mi><\/msub>/);
verify('["fun", "f", [",", "a", "b"]]', /<mrow id="[0-9]*"><mi id="[0-9]*">f<\/mi><mfenced[^>]*><mi[^>]*>a<\/mi><mi[^>]*>b<\/mi><\/mfenced><\/mrow>/);
// mfence is added to parent!
verify('[",", "a", "b"]', /<mi[^>]*>a<\/mi><mi[^>]*>b<\/mi>/);
verify('["<", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">&lt;<\/mo><mi id="[0-9]*">y<\/mi>/);
verify('["<=", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">&le;<\/mo><mi id="[0-9]*">y<\/mi>/);
verify('[">", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">&gt;<\/mo><mi id="[0-9]*">y<\/mi>/);
verify('[">=", "x", "y"]', /<mi id="[0-9]*">x<\/mi><mo id="[0-9]*">&ge;<\/mo><mi id="[0-9]*">y<\/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",\n [1,2]\n]');
}
function UnitTest() {
try {
EvalTest();
SerializerTest();
dp("test sucess!");
}catch(e) {
dp("test fail!");
dp(e);
}
}
</script>
<br>
<a name="input"></a>
<input value="Register" type="button" onclick="Register($('input').value);">
<br>
<div id="mathcanvas"></div>
<ul id="cutbuffer" contenteditable="true"></ul>
<hr>
<input value="FrozenedContent" type="button" onclick="FrozenedContent()">
<input value="Clear" type="button" onclick="onClear();"><br>
<textarea id="console" rows="10" cols="100" ></textarea><br>
<textarea id="input" rows="2" cols="100" onkeydown="OnInputKeyDown(event)" onfocus="OnInputFocus()" onblur="OnInputBlur()"></textarea><br>
<input value="Set" type="button" onclick="UpdateInputToCanvas();">
<hr>
<h3>キーバインド</h3>
<dl>
<dt>+</dt><dd>選択範囲にqueryした式を+で追加。選択されていなければ全体をxに置き換える。</dd>
<dt>*</dt><dd>選択範囲にqueryした式を*で追加</dd>
<dt>/</dt><dd>選択範囲をyで割る(分数になる)</dd>
<dt>-</dt><dd>選択範囲を-にする(トグル)</dd>
<dt>_</dt><dd>選択範囲にqueryした式を_で追加(下付きの添え字)</dd>
<dt>,</dt><dd>選択範囲が関数かmaxの時、引数にqueryした式を追加</dd>
<dt>&gt;</dt><dd>選択範囲を左辺として、大なりの不等式を作る</dd>
<dt>g</dt><dd>選択範囲を左辺として、大なりイコールの不等式を作る</dd>
<dt>&lt;</dt><dd>選択範囲を左辺として、小なりの不等式を作る</dd>
<dt>l</dt><dd>選択範囲を左辺として、小なりイコールの不等式を作る</dd>
<dt>=</dt><dd>選択範囲を左辺として、等式を作る</dd>
<dt>a</dt><dd>全て選択</dd>
<dt>c</dt><dd>選択範囲をコピー領域に追加</dd>
<dt>e</dt><dd>選択範囲が親と同じ複数項演算子なら親の子に参入(expand)</dd>
<dt>f</dt><dd>選択範囲を名前とした関数にする</dd>
<dt>i</dt><dd>選択範囲をインテグラルで囲む</dd>
<dt>m</dt><dd>選択範囲をmaxで囲む</dd>
<dt>M</dt><dd>選択範囲をminで囲む</dd>
<dt>p</dt><dd>コピーバッファを回転。Shiftキーと同時押しで逆回転</dd>
<dt>r</dt><dd>inputダイアログを表示し、そこに入力されたテキスト(または数字)で選択範囲を置き換える</dd>
<dt>s</dt><dd>選択範囲を逆数にする</dd>
<dt>v</dt><dd>選択されている部分をコピーバッファの先頭で上書き</dd>
<dt>w</dt><dd>選択範囲を広げる</dd>
<dt>x</dt><dd>選択範囲をコピー領域に追加して選択範囲を削除(parentが+か*の時だけ)</dd>
<dt>D</dt><dd>選択要素を偏微分</dd>
<dt>E</dt><dd>Export</dd>
<dt>I</dt><dd>Import</dd>
<dt>C-j (textarea内)</dt><dd>テキストエリア内を評価してmathcanvasに表示</dd>
</dl>
<hr>
以下は適当な例。<br>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mi>&HilbertSpace;</mi>
<mfenced>
<mi>x</mi>
<mi>y</mi>
</mfenced>
</math>
<h3>ToDo</h3>
<ul>
<li>exコマンド
<li>undoを付ける
<li>選択の移動(h, l?)
<ul>
<li>兄弟を移動
<li>子供に入っていく(a_xでaからxに行く、とか。兄弟を移動と一緒にすべき?)
</ul>
<li>先頭を選択、というキーが要る
<li>選択範囲のwidenの逆
<li>バグ: シングルクォートがexport時にエスケープされてない(からImportに失敗する)。
<li>バグ?: entity referenceがexportすると解決されちゃう。
<li>historyの式と式の間に変な空白がある
<li>1/aとかでa消したら1になって欲しい。指数乗も。
<li>-(a+b)でa+bをcutしたらこの項全体消えてほしい(+の時は消える)
<li>reviveの操作性改善
<ul>
<li><s>checkboxとボタンを無くす(代わりのブロックの色を変えて選択)</s>
<li><s>選択してr(仮)でrevive</s> (これ終わってるという認識でいいよね?)
<li>displayをinline-blockにしてみる
</ul>
<li>lim (リミット)
<li>空の時のvでl相当 (+してaしてvでいいんじゃね?)
<li>maxでwiden出来ないのを直す(もうなおった?)
<li>rでtex書式で入力出来たら神だなぁ
<li>historyに入ってるのをいちいちreviveせずにコピー出来たらなぁ
<li>式が整合的な状態で気軽にsnap shotを残す仕組み(Registerされた式の子供に入ってfoldingとか。チェックボックス復活?)
<li>+と*でarrayが伸びるようにする。
<li><s>AddArgument (',')もqueryするように変更</s>
<li><s>=全体を選択してる時のswitch (s)は左右入れ替えか。</s>
<li><s>lを無くす</s>
<li><s>ハットとか、一旦tとかで出しちゃうんじゃなくて、何乗するかqueryする方がいいかも。</s>
<li><s>^を入れる</s>
<li><s>integralを入れる</s>
<li><s>((-a)*b)を-(a*b)にする</s>手動で出来るかカット
<li><s>/を入れる</s>
<li><s>文字の入力を入れる(prompt)</s>
<li><s>-がトグルする様にする</s>
<li><s>項の削除を入れる(仕様は要検討)</s>
<li><s>aで全体を選択</s>
<li><s>選択している部分を親のarrayに展開する</s>
<li><s>空の時の+で単項をロード</s>
</ul>
<iframe id='iframe' style="display:none;"></iframe>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment