      1 /*
      2     Copyright 2008-2010,
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software: you can redistribute it and/or modify
     13     it under the terms of the GNU Lesser General Public License as published by
     14     the Free Software Foundation, either version 3 of the License, or
     15     (at your option) any later version.
     16
     17     JSXGraph is distributed in the hope that it will be useful,
     18     but WITHOUT ANY WARRANTY; without even the implied warranty of
     19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20     GNU Lesser General Public License for more details.
     21
     22     You should have received a copy of the GNU Lesser General Public License
     23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
     24
     25 */
     26
     27
     28 /**
     29  * Parser helper routines. The methods in here are for parsing expressions in Geonext Syntax.
     30  * @namespace
     31  */
     32
     33 JXG.GeonextParser = {};
     34
     35 /**
     36  * Converts expression of the form <i>leftop^rightop</i> into <i>Math.pow(leftop,rightop)</i>.
     37  * @param {String} te Expression of the form <i>leftop^rightop</i>
     38  * @type String
     39  * @return Converted expression.
     40  */
     41 JXG.GeonextParser.replacePow = function(te) {
     42     var count, pos, c,
     43         leftop, rightop, pre, p, left, i, right, expr;
     44     //te = te.replace(/\s+/g,''); // Loesche allen whitespace
     45                                 // Achtung: koennte bei Variablennamen mit Leerzeichen
     46                                 // zu Problemen fuehren.
     47
     48     te = te.replace(/(\s*)\^(\s*)/g,'\^'); // delete all whitespace immediately before and after all ^ operators
     49
     50 	//  Loop over all ^ operators
     51     i = te.indexOf('^');
     52     while (i>=0) {
     53 		// left and right are the substrings before, resp. after the ^ character
     54         left = te.slice(0,i);
     55         right = te.slice(i+1);
     56
     57 		// If there is a ")" immediately before the ^ operator, it can be the end of a
     58 		// (i) term in parenthesis
     59 		// (ii) function call
     60 		// (iii) method  call
     61 		// In either case, first the corresponding opening parenthesis is searched.
     62 		// This is the case, when count==0
     63         if (left.charAt(left.length-1)==')') {
     64             count = 1;
     65             pos = left.length-2;
     66             while (pos>=0 && count>0) {
     67                 c = left.charAt(pos);
     68                 if (c==')') { count++; }
     69                 else if (c=='(') { count--; }
     70                 pos--;
     71             }
     72             if (count==0) {
     73 				// Now, we have found the opning parenthesis and we have to look
     74 				// if it is (i), or (ii), (iii).
     75                 leftop = '';
     76                 pre = left.substring(0,pos+1);   // Search for F or p.M before (...)^
     77                 p = pos;
     78                 while (p>=0 && pre.substr(p,1).match(/([\w\.]+)/)) {
     79                     leftop = RegExp.$1+leftop;
     80                     p--;
     81                 }
     82                 leftop += left.substring(pos+1,left.length);
     83                 leftop = leftop.replace(/([\(\)\+\*\%\^\-\/\]\[])/g,"\\$1");
     84             } else {
     85 				throw new Error("JSXGraph: Missing '(' in expression");
     86 			}
     87         } else {
     88             //leftop = '[\\w\\.\\(\\)\\+\\*\\%\\^\\-\\/\\[\\]]+'; // former: \\w\\. . Doesn't work for sin(x^2)
     89    			// Otherwise, the operand has to be a constant (or variable).
     90          leftop = '[\\w\\.]+'; // former: \\w\\.
     91         }
     92 		// To the right of the ^ operator there also may be a function or method call
     93 		// or a term in parenthesis. Alos, ere we search for the closing
     94 		// parenthesis.
     95         if (right.match(/^([\w\.]*\()/)) {
     96             count = 1;
     97             pos = RegExp.$1.length;
     98             while (pos<right.length && count>0) {
     99                 c = right.charAt(pos);
    100                 if (c==')') { count--; }
    101                 else if (c=='(') { count++; }
    102                 pos++;
    103             }
    104             if (count==0) {
    105                 rightop = right.substring(0,pos);
    106                 rightop = rightop.replace(/([\(\)\+\*\%\^\-\/\[\]])/g,"\\$1");
    107             } else {
    108 				throw new Error("JSXGraph: Missing ')' in expression");
    109 			}
    110         } else {
    111             //rightop = '[\\w\\.\\(\\)\\+\\*\\%\\^\\-\\/\\[\\]]+';  // ^b , see leftop. Doesn't work for sin(x^2)
    112 			// Otherwise, the operand has to be a constant (or variable).
    113             rightop = '[\\w\\.]+';
    114         }
    115 		// Now, we have the two operands and replace ^ by JXG.Math.pow
    116         expr = new RegExp('(' + leftop + ')\\^(' + rightop + ')');
    117         te = te.replace(expr,"JXG.Math.pow($1,$2)");
    118         i = te.indexOf('^');
    119     }
    120     return te;
    121 };
    122
    123 /**
    124  * Converts expression of the form <i>If(a,b,c)</i> into <i>(a)?(b):(c)/i>.
    125  * @param {String} te Expression of the form <i>If(a,b,c)</i>
    126  * @type String
    127  * @return Converted expression.
    128  */
    129 JXG.GeonextParser.replaceIf = function(te) {
    130     var s = '',
    131         left, right,
    132         first = null,
    133         second = null,
    134         third = null,
    135         i, pos, count, k1, k2, c, meat;
    136
    137     i = te.indexOf('If(');
    138     if (i<0) { return te; }
    139
    140     te = te.replace(/""/g,'0'); // "" means not defined. Here, we replace it by 0
    141     while (i>=0) {
    142         left = te.slice(0,i);
    143         right = te.slice(i+3);
    144
    145         // Search the end of the If() command and take out the meat
    146         count = 1;
    147         pos = 0;
    148         k1 = -1;
    149         k2 = -1;
    150         while (pos<right.length && count>0) {
    151             c = right.charAt(pos);
    152             if (c==')') {
    153                 count--;
    154             } else if (c=='(') {
    155                 count++;
    156             } else if (c==',' && count==1) {
    157                 if (k1<0) {
    158                     k1 = pos; // first komma
    159                 } else {
    160                     k2 = pos; // second komma
    161                 }
    162             }
    163             pos++;
    164         }
    165         meat = right.slice(0,pos-1);
    166         right = right.slice(pos);
    167
    168         // Test the two kommas
    169         if (k1<0) { return ''; } // , missing
    170         if (k2<0) { return ''; } // , missing
    171
    172         first = meat.slice(0,k1);
    173         second = meat.slice(k1+1,k2);
    174         third = meat.slice(k2+1);
    175         first = this.replaceIf(first);    // Recurse
    176         second = this.replaceIf(second);  // Recurse
    177         third = this.replaceIf(third);    // Recurse
    178
    179         s += left + '((' + first + ')?' + '('+second+'):('+third+'))';
    180         te = right;
    181         first = null;
    182         second = null;
    183         i = te.indexOf('If(');
    184     }
    185     s += right;
    186     return s;
    187 };
    188
    189 /**
    190  * Replace _{} by <sub>
    191  * @param {String} the String containing _{}.
    192  * @type String
    193  * @return Given string with _{} replaced by <sub>.
    194  */
    195 JXG.GeonextParser.replaceSub = function(te) {
    196     if(te['indexOf']) {} else return te;
    197
    198     var i = te.indexOf('_{'),
    199         j;
    200     while (i>=0) {
    201         te = te.substr(0,i)+te.substr(i).replace(/_\{/,'<sub>');
    202         j = te.substr(i).indexOf('}');
    203         if (j>=0) {
    204             te = te.substr(0,j)+te.substr(j).replace(/\}/,'</sub>');
    205         }
    206         i = te.indexOf('_{');
    207     }
    208
    209     i = te.indexOf('_');
    210     while (i>=0) {
    211         te = te.substr(0,i)+te.substr(i).replace(/_(.?)/,'<sub>$1</sub>');
    212         i = te.indexOf('_');
    213     }
    214     return te;
    215 };
    216
    217 /**
    218  * Replace ^{} by <sup>
    219  * @param {String} the String containing ^{}.
    220  * @type String
    221  * @return Given string with ^{} replaced by <sup>.
    222  */
    223 JXG.GeonextParser.replaceSup = function(te) {
    224     if(te['indexOf']) {} else return te;
    225
    226     var i = te.indexOf('^{'),
    227         j;
    228     while (i>=0) {
    229         te = te.substr(0,i)+te.substr(i).replace(/\^\{/,'<sup>');
    230         j = te.substr(i).indexOf('}');
    231         if (j>=0) {
    232             te = te.substr(0,j)+te.substr(j).replace(/\}/,'</sup>');
    233         }
    234         i = te.indexOf('^{');
    235     }
    236
    237     i = te.indexOf('^');
    238     while (i>=0) {
    239         te = te.substr(0,i)+te.substr(i).replace(/\^(.?)/,'<sup>$1</sup>');
    240         i = te.indexOf('^');
    241     }
    242
    243     return te;
    244 };
    245
    246 /**
    247  * Replace an element's name in terms by an element's id.
    248  * @param term Term containing names of elements.
    249  * @param board Reference to the board the elements are on.
    250  * @return The same string with names replaced by ids.
    251  **/
    252 JXG.GeonextParser.replaceNameById = function(/** string */ term, /** JXG.Board */ board) /** string */ {
    253     var pos = 0, end, elName, el, i,
    254         funcs = ['X','Y','L','V'];
    255
    256     //
    257     // Find X(el), Y(el), ...
    258     // All functions declared in funcs
    259     for (i=0;i<funcs.length;i++) {
    260         pos = term.indexOf(funcs[i]+'(');
    261         while (pos>=0) {
    262             if (pos>=0) {
    263                 end = term.indexOf(')',pos+2);
    264                 if (end>=0) {
    265                     elName = term.slice(pos+2,end);
    266                     elName = elName.replace(/\\(['"])?/g,"$1");
    267                     el = board.elementsByName[elName];
    268                     term = term.slice(0,pos+2) + el.id +  term.slice(end);
    269                 }
    270             }
    271             end = term.indexOf(')',pos+2);
    272             pos = term.indexOf(funcs[i]+'(',end);
    273         }
    274     }
    275
    276     pos = term.indexOf('Dist(');
    277     while (pos>=0) {
    278         if (pos>=0) {
    279             end = term.indexOf(',',pos+5);
    280             if (end>=0) {
    281                 elName = term.slice(pos+5,end);
    282                 elName = elName.replace(/\\(['"])?/g,"$1");
    283                 el = board.elementsByName[elName];
    284                 term = term.slice(0,pos+5) + el.id +  term.slice(end);
    285             }
    286         }
    287         end = term.indexOf(',',pos+5);
    288         pos = term.indexOf(',',end);
    289         end = term.indexOf(')',pos+1);
    290         if (end>=0) {
    291             elName = term.slice(pos+1,end);
    292             elName = elName.replace(/\\(['"])?/g,"$1");
    293             el = board.elementsByName[elName];
    294             term = term.slice(0,pos+1) + el.id +  term.slice(end);
    295         }
    296         end = term.indexOf(')',pos+1);
    297         pos = term.indexOf('Dist(',end);
    298     }
    299
    300     funcs = ['Deg','Rad'];
    301     for (i=0;i<funcs.length;i++) {
    302         pos = term.indexOf(funcs[i]+'(');
    303         while (pos>=0) {
    304             if (pos>=0) {
    305                 end = term.indexOf(',',pos+4);
    306                 if (end>=0) {
    307                     elName = term.slice(pos+4,end);
    308                     elName = elName.replace(/\\(['"])?/g,"$1");
    309                     el = board.elementsByName[elName];
    310                     term = term.slice(0,pos+4) + el.id +  term.slice(end);
    311                 }
    312             }
    313             end = term.indexOf(',',pos+4);
    314             pos = term.indexOf(',',end);
    315             end = term.indexOf(',',pos+1);
    316             if (end>=0) {
    317                 elName = term.slice(pos+1,end);
    318                 elName = elName.replace(/\\(['"])?/g,"$1");
    319                 el = board.elementsByName[elName];
    320                 term = term.slice(0,pos+1) + el.id +  term.slice(end);
    321             }
    322             end = term.indexOf(',',pos+1);
    323             pos = term.indexOf(',',end);
    324             end = term.indexOf(')',pos+1);
    325             if (end>=0) {
    326                 elName = term.slice(pos+1,end);
    327                 elName = elName.replace(/\\(['"])?/g,"$1");
    328                 el = board.elementsByName[elName];
    329                 term = term.slice(0,pos+1) + el.id +  term.slice(end);
    330             }
    331             end = term.indexOf(')',pos+1);
    332             pos = term.indexOf(funcs[i]+'(',end);
    333         }
    334     }
    335     return term;
    336 };
    337
    338 /**
    339  * Replaces element ids in terms by element this.board.objects['id'].
    340  * @param term A GEONE<sub>x</sub>T function string with JSXGraph ids in it.
    341  * @return The input string with element ids replaced by this.board.objects["id"].
    342  **/
    343 JXG.GeonextParser.replaceIdByObj = function(/** string */ term) /** string */ {
    344     var expr = /(X|Y|L)\(([\w_]+)\)/g;  // Suche "X(gi23)" oder "Y(gi23A)" und wandle in objects['gi23'].X() um.
    345     term = term.replace(expr,"this.board.objects[\"$2\"].$1()");
    346
    347     expr = /(V)\(([\w_]+)\)/g;  // Suche "X(gi23)" oder "Y(gi23A)" und wandle in objects['gi23'].X() um.
    348     term = term.replace(expr,"this.board.objects[\"$2\"].Value()");
    349
    350     expr = /(Dist)\(([\w_]+),([\w_]+)\)/g;  //
    351     term = term.replace(expr,'this.board.objects[\"$2\"].Dist(this.board.objects[\"$3\"])');
    352
    353     expr = /(Deg)\(([\w_]+),([ \w\[\w_]+),([\w_]+)\)/g;  //
    354     term = term.replace(expr,'JXG.Math.Geometry.trueAngle(this.board.objects[\"$2\"],this.board.objects[\"$3\"],this.board.objects[\"$4\"])');
    355
    356     expr = /Rad\(([\w_]+),([\w_]+),([\w_]+)\)/g;  // Suche Rad('gi23','gi24','gi25')
    357     term = term.replace(expr,'JXG.Math.Geometry.rad(this.board.objects[\"$1\"],this.board.objects[\"$2\"],this.board.objects[\"$3\"])');
    358     return term;
    359 };
    360
    361 /**
    362  * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JavaScript syntax.
    363  * @param {String} term Expression in GEONExT syntax
    364  * @type String
    365  * @return Given expression translated to JavaScript.
    366  */
    367 JXG.GeonextParser.geonext2JS = function(term, board) {
    368     var expr, newterm, i,
    369         from = ['Abs', 'ACos', 'ASin', 'ATan','Ceil','Cos','Exp','Factorial','Floor','Log','Max','Min','Random','Round','Sin','Sqrt','Tan','Trunc'],
    370         to =   ['Math.abs', 'Math.acos', 'Math.asin', 'Math.atan', 'Math.ceil', 'Math.cos', 'Math.exp', 'JXG.Math.factorial','Math.floor', 'Math.log', 'Math.max', 'Math.min', 'Math.random', 'this.board.round', 'Math.sin', 'Math.sqrt', 'Math.tan', 'Math.ceil'];
    371     // removed: 'Pow'  -> Math.pow
    372
    373     //term = JXG.unescapeHTML(term);  // This replaces > by >, < by < and & by &. But it is to strict.
    374     term = term.replace(/</g,'<'); // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan
    375     term = term.replace(/>/g,'>');
    376     term = term.replace(/&/g,'&');
    377
    378     // Umwandeln der GEONExT-Syntax in JavaScript-Syntax
    379     newterm = term;
    380     newterm = this.replaceNameById(newterm, board);
    381     newterm = this.replaceIf(newterm);
    382     // Exponentiations-Problem x^y -> Math(exp(x,y).
    383     newterm = this.replacePow(newterm);
    384     newterm = this.replaceIdByObj(newterm);
    385     for (i=0; i<from.length; i++) {
    386         expr = new RegExp(['(\\W|^)(',from[i],')'].join(''),"ig");  // sin -> Math.sin and asin -> Math.asin
    387         newterm = newterm.replace(expr,['$1',to[i]].join(''));
    388     }
    389     newterm = newterm.replace(/True/g,'true');
    390     newterm = newterm.replace(/False/g,'false');
    391     newterm = newterm.replace(/fasle/g,'false');
    392
    393     newterm = newterm.replace(/Pi/g,'Math.PI');
    394     return newterm;
    395 };
    396
    397 /**
    398  * Finds dependencies in a given term and resolves them by adding the
    399  * dependent object to the found objects child elements.
    400  * @param {JXG.GeometryElement} me Object depending on objects in given term.
    401  * @param {String} term String containing dependencies for the given object.
    402  * @param {JXG.Board} [board=me.board] Reference to a board
    403  */
    404 JXG.GeonextParser.findDependencies = function(me, term, board) {
    405     if(typeof board=='undefined')
    406         board = me.board;
    407
    408     var elements = board.elementsByName,
    409         el, expr, elmask;
    410
    411     for (el in elements) {
    412         if (el != me.name) {
    413             if(elements[el].type == JXG.OBJECT_TYPE_TEXT) {
    414                 if(!elements[el].visProp.islabel) {
    415                     elmask = el.replace(/\[/g,'\\[');
    416                     elmask = elmask.replace(/\]/g,'\\]');
    417                     expr = new RegExp("\\(\(\[\\w\\[\\]'_ \]+,\)*\("+elmask+"\)\(,\[\\w\\[\\]'_ \]+\)*\\)","g");  // Searches (A), (A,B),(A,B,C)
    418                     if (term.search(expr)>=0) {
    419                         elements[el].addChild(me);
    420                     }
    421                 }
    422             }
    423             else {
    424                 elmask = el.replace(/\[/g,'\\[');
    425                 elmask = elmask.replace(/\]/g,'\\]');
    426                 expr = new RegExp("\\(\(\[\\w\\[\\]'_ \]+,\)*\("+elmask+"\)\(,\[\\w\\[\\]'_ \]+\)*\\)","g");  // Searches (A), (A,B),(A,B,C)
    427                 if (term.search(expr)>=0) {
    428                     elements[el].addChild(me);
    429                 }
    430             }
    431         }
    432     }
    433 };
    434
    435 /**
    436  * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JavaScript syntax.
    437  * @param {String} term Expression in GEONExT syntax
    438  * @type String
    439  * @return Given expression translated to JavaScript.
    440  */
    441 JXG.GeonextParser.gxt2jc = function(term, board) {
    442     var newterm,
    443 		from = ['Sqrt'],
    444 		to = ['sqrt'];
    445
    446     term = term.replace(/</g,'<'); // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan
    447     term = term.replace(/>/g,'>');
    448     term = term.replace(/&/g,'&');
    449     newterm = term;
    450     newterm = this.replaceNameById2(newterm, board);
    451 	/*
    452     for (i=0; i<from.length; i++) {
    453         expr = new RegExp(['(\\W|^)(',from[i],')'].join(''),"ig");  // sin -> Math.sin and asin -> Math.asin
    454         newterm = newterm.replace(expr,['$1',to[i]].join(''));
    455     }
    456 	*/
    457     newterm = newterm.replace(/True/g,'true');
    458     newterm = newterm.replace(/False/g,'false');
    459     newterm = newterm.replace(/fasle/g,'false');
    460
    461     return newterm;
    462 };
    463
    464 /**
    465  * Replace an element's name in terms by an element's id.
    466  * @param term Term containing names of elements.
    467  * @param board Reference to the board the elements are on.
    468  * @return The same string with names replaced by ids.
    469  **/
    470 JXG.GeonextParser.replaceNameById2 = function(/** string */ term, /** JXG.Board */ board) /** string */ {
    471     var pos = 0, end, elName, el, i,
    472         funcs = ['X','Y','L','V'];
    473
    474     //
    475     // Find X(el), Y(el), ...
    476     // All functions declared in funcs
    477     for (i=0;i<funcs.length;i++) {
    478         pos = term.indexOf(funcs[i]+'(');
    479         while (pos>=0) {
    480             if (pos>=0) {
    481                 end = term.indexOf(')',pos+2);
    482                 if (end>=0) {
    483                     elName = term.slice(pos+2,end);
    484                     elName = elName.replace(/\\(['"])?/g,"$1");
    485                     el = board.elementsByName[elName];
    486                     term = term.slice(0,pos+2) + "$('"+el.id +"')" +  term.slice(end);
    487                 }
    488             }
    489             end = term.indexOf(')',pos+2);
    490             pos = term.indexOf(funcs[i]+'(',end);
    491         }
    492     }
    493
    494     pos = term.indexOf('Dist(');
    495     while (pos>=0) {
    496         if (pos>=0) {
    497             end = term.indexOf(',',pos+5);
    498             if (end>=0) {
    499                 elName = term.slice(pos+5,end);
    500                 elName = elName.replace(/\\(['"])?/g,"$1");
    501                 el = board.elementsByName[elName];
    502                 term = term.slice(0,pos+5) + "$('"+el.id +"')" +  term.slice(end);
    503             }
    504         }
    505         end = term.indexOf(',',pos+5);
    506         pos = term.indexOf(',',end);
    507         end = term.indexOf(')',pos+1);
    508         if (end>=0) {
    509             elName = term.slice(pos+1,end);
    510             elName = elName.replace(/\\(['"])?/g,"$1");
    511             el = board.elementsByName[elName];
    512             term = term.slice(0,pos+1) + "$('"+el.id +"')" +  term.slice(end);
    513         }
    514         end = term.indexOf(')',pos+1);
    515         pos = term.indexOf('Dist(',end);
    516     }
    517
    518     funcs = ['Deg','Rad'];
    519     for (i=0;i<funcs.length;i++) {
    520         pos = term.indexOf(funcs[i]+'(');
    521         while (pos>=0) {
    522             if (pos>=0) {
    523                 end = term.indexOf(',',pos+4);
    524                 if (end>=0) {
    525                     elName = term.slice(pos+4,end);
    526                     elName = elName.replace(/\\(['"])?/g,"$1");
    527                     el = board.elementsByName[elName];
    528                     term = term.slice(0,pos+4) + "$('"+el.id +"')" +  term.slice(end);
    529                 }
    530             }
    531             end = term.indexOf(',',pos+4);
    532             pos = term.indexOf(',',end);
    533             end = term.indexOf(',',pos+1);
    534             if (end>=0) {
    535                 elName = term.slice(pos+1,end);
    536                 elName = elName.replace(/\\(['"])?/g,"$1");
    537                 el = board.elementsByName[elName];
    538                 term = term.slice(0,pos+1) + "$('"+el.id +"')" +  term.slice(end);
    539             }
    540             end = term.indexOf(',',pos+1);
    541             pos = term.indexOf(',',end);
    542             end = term.indexOf(')',pos+1);
    543             if (end>=0) {
    544                 elName = term.slice(pos+1,end);
    545                 elName = elName.replace(/\\(['"])?/g,"$1");
    546                 el = board.elementsByName[elName];
    547                 term = term.slice(0,pos+1) + "$('"+el.id +"')" +  term.slice(end);
    548             }
    549             end = term.indexOf(')',pos+1);
    550             pos = term.indexOf(funcs[i]+'(',end);
    551         }
    552     }
    553     return term;
    554 };
    555
    556