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 
359     expr = /N\((.+)\)/g;  // 
360     term = term.replace(expr,'($1)');
361 
362     return term;
363 };
364 
365 /**
366  * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JavaScript syntax.
367  * @param {String} term Expression in GEONExT syntax
368  * @type String
369  * @return Given expression translated to JavaScript.
370  */
371 JXG.GeonextParser.geonext2JS = function(term, board) {
372     var expr, newterm, i,
373         from = ['Abs', 'ACos', 'ASin', 'ATan','Ceil','Cos','Exp','Factorial','Floor','Log','Max','Min','Random','Round','Sin','Sqrt','Tan','Trunc'], 
374         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'];
375     // removed: 'Pow'  -> Math.pow
376     
377     //term = JXG.unescapeHTML(term);  // This replaces > by >, < by < and & by &. But it is to strict. 
378     term = term.replace(/</g,'<'); // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan
379     term = term.replace(/>/g,'>'); 
380     term = term.replace(/&/g,'&'); 
381     
382     // Umwandeln der GEONExT-Syntax in JavaScript-Syntax
383     newterm = term;
384     newterm = this.replaceNameById(newterm, board);
385     newterm = this.replaceIf(newterm);
386     // Exponentiations-Problem x^y -> Math(exp(x,y).
387     newterm = this.replacePow(newterm);
388     newterm = this.replaceIdByObj(newterm);
389     for (i=0; i<from.length; i++) {
390         expr = new RegExp(['(\\W|^)(',from[i],')'].join(''),"ig");  // sin -> Math.sin and asin -> Math.asin 
391         newterm = newterm.replace(expr,['$1',to[i]].join(''));
392     } 
393     newterm = newterm.replace(/True/g,'true');
394     newterm = newterm.replace(/False/g,'false');
395     newterm = newterm.replace(/fasle/g,'false');
396 
397     newterm = newterm.replace(/Pi/g,'Math.PI');
398     return newterm;
399 };
400 
401 /**
402  * Finds dependencies in a given term and resolves them by adding the
403  * dependent object to the found objects child elements.
404  * @param {JXG.GeometryElement} me Object depending on objects in given term.
405  * @param {String} term String containing dependencies for the given object.
406  * @param {JXG.Board} [board=me.board] Reference to a board
407  */
408 JXG.GeonextParser.findDependencies = function(me, term, board) {
409     if(typeof board=='undefined')
410         board = me.board;
411 
412     var elements = board.elementsByName,
413         el, expr, elmask;
414 
415     for (el in elements) {
416         if (el != me.name) {
417             if(elements[el].type == JXG.OBJECT_TYPE_TEXT) {
418                 if(!elements[el].visProp.islabel) {
419                     elmask = el.replace(/\[/g,'\\[');
420                     elmask = elmask.replace(/\]/g,'\\]');
421                     expr = new RegExp("\\(\(\[\\w\\[\\]'_ \]+,\)*\("+elmask+"\)\(,\[\\w\\[\\]'_ \]+\)*\\)","g");  // Searches (A), (A,B),(A,B,C)
422                     if (term.search(expr)>=0) {
423                         elements[el].addChild(me);
424                     }
425                 }
426             }
427             else {
428                 elmask = el.replace(/\[/g,'\\[');
429                 elmask = elmask.replace(/\]/g,'\\]');
430                 expr = new RegExp("\\(\(\[\\w\\[\\]'_ \]+,\)*\("+elmask+"\)\(,\[\\w\\[\\]'_ \]+\)*\\)","g");  // Searches (A), (A,B),(A,B,C)
431                 if (term.search(expr)>=0) {
432                     elements[el].addChild(me);
433                 }
434             }
435         }
436     }
437 };
438 
439 /**
440  * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JavaScript syntax.
441  * @param {String} term Expression in GEONExT syntax
442  * @type String
443  * @return Given expression translated to JavaScript.
444  */
445 JXG.GeonextParser.gxt2jc = function(term, board) {
446     var newterm,
447 		from = ['Sqrt'],
448 		to = ['sqrt'];
449     
450     term = term.replace(/</g,'<'); // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan
451     term = term.replace(/>/g,'>'); 
452     term = term.replace(/&/g,'&'); 
453     newterm = term;
454     newterm = this.replaceNameById2(newterm, board);
455 	/*
456     for (i=0; i<from.length; i++) {
457         expr = new RegExp(['(\\W|^)(',from[i],')'].join(''),"ig");  // sin -> Math.sin and asin -> Math.asin 
458         newterm = newterm.replace(expr,['$1',to[i]].join(''));
459     } 
460 	*/
461     newterm = newterm.replace(/True/g,'true');
462     newterm = newterm.replace(/False/g,'false');
463     newterm = newterm.replace(/fasle/g,'false');
464 
465     return newterm;
466 };
467 
468 /**
469  * Replace an element's name in terms by an element's id.
470  * @param term Term containing names of elements.
471  * @param board Reference to the board the elements are on.
472  * @return The same string with names replaced by ids.
473  **/
474 JXG.GeonextParser.replaceNameById2 = function(/** string */ term, /** JXG.Board */ board) /** string */ {
475     var pos = 0, end, elName, el, i,
476         funcs = ['X','Y','L','V'];
477 
478     //    
479     // Find X(el), Y(el), ... 
480     // All functions declared in funcs
481     for (i=0;i<funcs.length;i++) {
482         pos = term.indexOf(funcs[i]+'(');
483         while (pos>=0) {
484             if (pos>=0) {
485                 end = term.indexOf(')',pos+2);
486                 if (end>=0) {
487                     elName = term.slice(pos+2,end);
488                     elName = elName.replace(/\\(['"])?/g,"$1");
489                     el = board.elementsByName[elName];
490                     term = term.slice(0,pos+2) + "$('"+el.id +"')" +  term.slice(end);
491                 }
492             }
493             end = term.indexOf(')',pos+2);
494             pos = term.indexOf(funcs[i]+'(',end);
495         }
496     }
497 
498     pos = term.indexOf('Dist(');
499     while (pos>=0) {
500         if (pos>=0) {
501             end = term.indexOf(',',pos+5);
502             if (end>=0) {
503                 elName = term.slice(pos+5,end);
504                 elName = elName.replace(/\\(['"])?/g,"$1");
505                 el = board.elementsByName[elName];
506                 term = term.slice(0,pos+5) + "$('"+el.id +"')" +  term.slice(end);
507             }
508         }
509         end = term.indexOf(',',pos+5);
510         pos = term.indexOf(',',end);
511         end = term.indexOf(')',pos+1);
512         if (end>=0) {
513             elName = term.slice(pos+1,end);
514             elName = elName.replace(/\\(['"])?/g,"$1");
515             el = board.elementsByName[elName];
516             term = term.slice(0,pos+1) + "$('"+el.id +"')" +  term.slice(end);
517         }
518         end = term.indexOf(')',pos+1);
519         pos = term.indexOf('Dist(',end);
520     }
521 
522     funcs = ['Deg','Rad'];
523     for (i=0;i<funcs.length;i++) {
524         pos = term.indexOf(funcs[i]+'(');
525         while (pos>=0) {
526             if (pos>=0) {
527                 end = term.indexOf(',',pos+4);
528                 if (end>=0) {
529                     elName = term.slice(pos+4,end);
530                     elName = elName.replace(/\\(['"])?/g,"$1");
531                     el = board.elementsByName[elName];
532                     term = term.slice(0,pos+4) + "$('"+el.id +"')" +  term.slice(end);
533                 }
534             }
535             end = term.indexOf(',',pos+4);
536             pos = term.indexOf(',',end);
537             end = term.indexOf(',',pos+1);
538             if (end>=0) {
539                 elName = term.slice(pos+1,end);
540                 elName = elName.replace(/\\(['"])?/g,"$1");
541                 el = board.elementsByName[elName];
542                 term = term.slice(0,pos+1) + "$('"+el.id +"')" +  term.slice(end);
543             }
544             end = term.indexOf(',',pos+1);
545             pos = term.indexOf(',',end);
546             end = term.indexOf(')',pos+1);
547             if (end>=0) {
548                 elName = term.slice(pos+1,end);
549                 elName = elName.replace(/\\(['"])?/g,"$1");
550                 el = board.elementsByName[elName];
551                 term = term.slice(0,pos+1) + "$('"+el.id +"')" +  term.slice(end);
552             }
553             end = term.indexOf(')',pos+1);
554             pos = term.indexOf(funcs[i]+'(',end);
555         }
556     }
557     return term;
558 };
559 
560