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  */
 31 
 32 JXG.GeonextParser = {};
 33 
 34 /**
 35  * Converts expression of the form <i>leftop^rightop</i> into <i>Math.pow(leftop,rightop)</i>.
 36  * @param {String} te Expression of the form <i>leftop^rightop</i>
 37  * @type String
 38  * @return Converted expression.
 39  */
 40 JXG.GeonextParser.replacePow = function(te) {
 41     var count, pos, c,
 42         leftop, rightop, pre, p, left, i, right, expr;
 43     //te = te.replace(/\s+/g,''); // Loesche allen whitespace
 44                                 // Achtung: koennte bei Variablennamen mit Leerzeichen
 45                                 // zu Problemen fuehren.
 46                                 
 47     te = te.replace(/(\s*)\^(\s*)/g,'\^'); // delete all whitespace immediately before and after all ^ operators
 48 
 49 	//  Loop over all ^ operators
 50     i = te.indexOf('^');
 51     while (i>=0) {
 52 		// left and right are the substrings before, resp. after the ^ character
 53         left = te.slice(0,i);
 54         right = te.slice(i+1);
 55 
 56 		// If there is a ")" immediately before the ^ operator, it can be the end of a
 57 		// (i) term in parenthesis
 58 		// (ii) function call
 59 		// (iii) method  callthrow new Error("JSXGraph: Can't create Sector with parent types
 60 		// In either case, first the corresponding opening parenthesis is searched.
 61 		// This is the case, when count==0
 62         if (left.charAt(left.length-1)==')') {
 63             count = 1;
 64             pos = left.length-2;
 65             while (pos>=0 && count>0) {
 66                 c = left.charAt(pos);
 67                 if (c==')') { count++; }
 68                 else if (c=='(') { count--; }
 69                 pos--;
 70             }   
 71             if (count==0) {
 72 				// Now, we have found the opning parenthesis and we have to look
 73 				// if it is (i), or (ii), (iii).
 74                 leftop = '';
 75                 pre = left.substring(0,pos+1);   // Search for F or p.M before (...)^
 76                 p = pos;
 77                 while (p>=0 && pre.substr(p,1).match(/([\w\.]+)/)) { 
 78                     leftop = RegExp.$1+leftop;
 79                     p--;
 80                 }
 81                 leftop += left.substring(pos+1,left.length);
 82                 leftop = leftop.replace(/([\(\)\+\*\%\^\-\/\]\[])/g,"\\$1");
 83             } else {
 84 				throw new Error("JSXGraph: Missing '(' in expression");
 85 			}
 86         } else {
 87             //leftop = '[\\w\\.\\(\\)\\+\\*\\%\\^\\-\\/\\[\\]]+'; // former: \\w\\. . Doesn't work for sin(x^2)
 88    			// Otherwise, the operand has to be a constant (or variable).
 89          leftop = '[\\w\\.]+'; // former: \\w\\.
 90         }
 91 		// To the right of the ^ operator there also may be a function or method call
 92 		// or a term in parenthesis. Alos, ere we search for the closing
 93 		// parenthesis.
 94         if (right.match(/^([\w\.]*\()/)) {
 95             count = 1;
 96             pos = RegExp.$1.length;
 97             while (pos<right.length && count>0) {
 98                 c = right.charAt(pos);
 99                 if (c==')') { count--; }
100                 else if (c=='(') { count++; }
101                 pos++;
102             }
103             if (count==0) {
104                 rightop = right.substring(0,pos);
105                 rightop = rightop.replace(/([\(\)\+\*\%\^\-\/\[\]])/g,"\\$1");
106             } else {
107 				throw new Error("JSXGraph: Missing ')' in expression");
108 			}
109         } else {
110             //rightop = '[\\w\\.\\(\\)\\+\\*\\%\\^\\-\\/\\[\\]]+';  // ^b , see leftop. Doesn't work for sin(x^2)
111 			// Otherwise, the operand has to be a constant (or variable).
112             rightop = '[\\w\\.]+';  
113         }
114 		// Now, we have the two operands and replace ^ by JXG.Math.pow
115         expr = new RegExp('(' + leftop + ')\\^(' + rightop + ')');
116         te = te.replace(expr,"JXG.Math.pow($1,$2)");
117         i = te.indexOf('^');
118     }
119     return te;
120 };
121 
122 /**
123  * Converts expression of the form <i>If(a,b,c)</i> into <i>(a)?(b):(c)/i>.
124  * @param {String} te Expression of the form <i>If(a,b,c)</i>
125  * @type String
126  * @return Converted expression.
127  */
128 JXG.GeonextParser.replaceIf = function(te) {
129     var s = '',
130         left, right,
131         first = null,
132         second = null,
133         third = null,
134         i, pos, count, k1, k2, c, meat;
135     
136     i = te.indexOf('If(');
137     if (i<0) { return te; }
138 
139     te = te.replace(/""/g,'0'); // "" means not defined. Here, we replace it by 0
140     while (i>=0) {
141         left = te.slice(0,i);
142         right = te.slice(i+3); 
143         
144         // Search the end of the If() command and take out the meat
145         count = 1;
146         pos = 0;
147         k1 = -1;
148         k2 = -1;
149         while (pos<right.length && count>0) {
150             c = right.charAt(pos);
151             if (c==')') { 
152                 count--;
153             } else if (c=='(') {
154                 count++;
155             } else if (c==',' && count==1) {
156                 if (k1<0) { 
157                     k1 = pos; // first komma
158                 } else {
159                     k2 = pos; // second komma
160                 }
161             }
162             pos++;
163         } 
164         meat = right.slice(0,pos-1);
165         right = right.slice(pos);
166         
167         // Test the two kommas
168         if (k1<0) { return ''; } // , missing
169         if (k2<0) { return ''; } // , missing
170         
171         first = meat.slice(0,k1);
172         second = meat.slice(k1+1,k2);
173         third = meat.slice(k2+1);
174         first = this.replaceIf(first);    // Recurse
175         second = this.replaceIf(second);  // Recurse
176         third = this.replaceIf(third);    // Recurse
177 
178         s += left + '((' + first + ')?' + '('+second+'):('+third+'))';  
179         te = right;
180         first = null;
181         second = null;
182         i = te.indexOf('If(');
183     }
184     s += right;
185     return s;
186 };
187 
188 /**
189  * Replace _{} by <sub>
190  * @param {String} te String containing _{}.
191  * @type String
192  * @return Given string with _{} replaced by <sub>.
193  */
194 JXG.GeonextParser.replaceSub = function(te) {
195     if(te['indexOf']) {} else return te;
196 
197     var i = te.indexOf('_{'),
198         j;
199     while (i>=0) {
200         te = te.substr(0,i)+te.substr(i).replace(/_\{/,'<sub>');
201         j = te.substr(i).indexOf('}');
202         if (j>=0) {
203             te = te.substr(0,j)+te.substr(j).replace(/\}/,'</sub>');
204         }
205         i = te.indexOf('_{');
206     }
207 
208     i = te.indexOf('_');
209     while (i>=0) {
210         te = te.substr(0,i)+te.substr(i).replace(/_(.?)/,'<sub>$1</sub>');
211         i = te.indexOf('_');
212     }
213     return te;
214 };
215 
216 /**
217  * Replace ^{} by <sup>
218  * @param {String} te String containing ^{}.
219  * @type String
220  * @return Given string with ^{} replaced by <sup>.
221  */
222 JXG.GeonextParser.replaceSup = function(te) {
223     if(te['indexOf']) {} else return te;
224 
225     var i = te.indexOf('^{'),
226         j;
227     while (i>=0) {
228         te = te.substr(0,i)+te.substr(i).replace(/\^\{/,'<sup>');
229         j = te.substr(i).indexOf('}');
230         if (j>=0) {
231             te = te.substr(0,j)+te.substr(j).replace(/\}/,'</sup>');
232         }
233         i = te.indexOf('^{');
234     }
235 
236     i = te.indexOf('^');
237     while (i>=0) {
238         te = te.substr(0,i)+te.substr(i).replace(/\^(.?)/,'<sup>$1</sup>');
239         i = te.indexOf('^');
240     }
241 
242     return te;
243 };
244 
245 /**
246  * Replace an element's name in terms by an element's id.
247  * @param term Term containing names of elements.
248  * @param board Reference to the board the elements are on.
249  * @return The same string with names replaced by ids.
250  **/
251 JXG.GeonextParser.replaceNameById = function(/** string */ term, /** JXG.Board */ board) /** string */ {
252     var pos = 0, end, elName, el, i,
253         funcs = ['X','Y','L','V'];
254     
255     for (i=0;i<funcs.length;i++) {
256         pos = term.indexOf(funcs[i]+'(');
257         while (pos>=0) {
258             if (pos>=0) {
259                 end = term.indexOf(')',pos+2);
260                 if (end>=0) {
261                     elName = term.slice(pos+2,end);
262                     elName = elName.replace(/\\(['"])?/g,"$1");
263                     el = board.elementsByName[elName];
264                     term = term.slice(0,pos+2) + el.id +  term.slice(end);
265                 }
266             }
267             end = term.indexOf(')',pos+2);
268             pos = term.indexOf(funcs[i]+'(',end);
269         }
270     }
271 
272     pos = term.indexOf('Dist(');
273     while (pos>=0) {
274         if (pos>=0) {
275             end = term.indexOf(',',pos+5);
276             if (end>=0) {
277                 elName = term.slice(pos+5,end);
278                 elName = elName.replace(/\\(['"])?/g,"$1");
279                 el = board.elementsByName[elName];
280                 term = term.slice(0,pos+5) + el.id +  term.slice(end);
281             }
282         }
283         end = term.indexOf(',',pos+5);
284         pos = term.indexOf(',',end);
285         end = term.indexOf(')',pos+1);
286         if (end>=0) {
287             elName = term.slice(pos+1,end);
288             elName = elName.replace(/\\(['"])?/g,"$1");
289             el = board.elementsByName[elName];
290             term = term.slice(0,pos+1) + el.id +  term.slice(end);
291         }
292         end = term.indexOf(')',pos+1);
293         pos = term.indexOf('Dist(',end);
294     }
295 
296     funcs = ['Deg','Rad'];
297     for (i=0;i<funcs.length;i++) {
298         pos = term.indexOf(funcs[i]+'(');
299         while (pos>=0) {
300             if (pos>=0) {
301                 end = term.indexOf(',',pos+4);
302                 if (end>=0) {
303                     elName = term.slice(pos+4,end);
304                     elName = elName.replace(/\\(['"])?/g,"$1");
305                     el = board.elementsByName[elName];
306                     term = term.slice(0,pos+4) + el.id +  term.slice(end);
307                 }
308             }
309             end = term.indexOf(',',pos+4);
310             pos = term.indexOf(',',end);
311             end = term.indexOf(',',pos+1);
312             if (end>=0) {
313                 elName = term.slice(pos+1,end);
314                 elName = elName.replace(/\\(['"])?/g,"$1");
315                 el = board.elementsByName[elName];
316                 term = term.slice(0,pos+1) + el.id +  term.slice(end);
317             }
318             end = term.indexOf(',',pos+1);
319             pos = term.indexOf(',',end);
320             end = term.indexOf(')',pos+1);
321             if (end>=0) {
322                 elName = term.slice(pos+1,end);
323                 elName = elName.replace(/\\(['"])?/g,"$1");
324                 el = board.elementsByName[elName];
325                 term = term.slice(0,pos+1) + el.id +  term.slice(end);
326             }
327             end = term.indexOf(')',pos+1);
328             pos = term.indexOf(funcs[i]+'(',end);
329         }
330     }
331     return term;
332 };
333 
334 /**
335  * Replaces element ids in terms by element this.board.objects['id'].
336  * @param term A GEONE<sub>x</sub>T function string with JSXGraph ids in it.
337  * @return The input string with element ids replaced by this.board.objects["id"]. 
338  **/
339 JXG.GeonextParser.replaceIdByObj = function(/** string */ term) /** string */ {
340     var expr = /(X|Y|L)\(([\w_]+)\)/g;  // Suche "X(gi23)" oder "Y(gi23A)" und wandle in objects['gi23'].X() um.
341     term = term.replace(expr,"this.board.objects[\"$2\"].$1()");
342     
343     expr = /(V)\(([\w_]+)\)/g;  // Suche "X(gi23)" oder "Y(gi23A)" und wandle in objects['gi23'].X() um.
344     term = term.replace(expr,"this.board.objects[\"$2\"].Value()");
345 
346     expr = /(Dist)\(([\w_]+),([\w_]+)\)/g;  // 
347     term = term.replace(expr,'this.board.objects[\"$2\"].Dist(this.board.objects[\"$3\"])');
348 
349     expr = /(Deg)\(([\w_]+),([ \w\[\w_]+),([\w_]+)\)/g;  // 
350     term = term.replace(expr,'JXG.Math.Geometry.trueAngle(this.board.objects[\"$2\"],this.board.objects[\"$3\"],this.board.objects[\"$4\"])');
351 
352     expr = /Rad\(([\w_]+),([\w_]+),([\w_]+)\)/g;  // Suche Rad('gi23','gi24','gi25')
353     term = term.replace(expr,'JXG.Math.Geometry.rad(this.board.objects[\"$1\"],this.board.objects[\"$2\"],this.board.objects[\"$3\"])');
354     return term;
355 };
356 
357 /**
358  * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JavaScript syntax.
359  * @param {String} term Expression in GEONExT syntax
360  * @type String
361  * @return Given expression translated to JavaScript.
362  */
363 JXG.GeonextParser.geonext2JS = function(term, board) {
364     var expr, newterm, i,
365         from = ['Abs', 'ACos', 'ASin', 'ATan','Ceil','Cos','Exp','Floor','Log','Max','Min','Random','Round','Sin','Sqrt','Tan','Trunc'], 
366         to =   ['Math.abs', 'Math.acos', 'Math.asin', 'Math.atan', 'Math.ceil', 'Math.cos', 'Math.exp', 'Math.floor', 'Math.log', 'Math.max', 'Math.min', 'Math.random', 'this.board.round', 'Math.sin', 'Math.sqrt', 'Math.tan', 'Math.ceil'];
367     // removed: 'Pow'  -> Math.pow
368     
369     //term = JXG.unescapeHTML(term);  // This replaces > by >, < by < and & by &. But it is to strict. 
370     term = term.replace(/</g,'<'); // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan
371     term = term.replace(/>/g,'>'); 
372     term = term.replace(/&/g,'&'); 
373     
374     // Umwandeln der GEONExT-Syntax in JavaScript-Syntax
375     newterm = term;
376     newterm = this.replaceNameById(newterm, board);
377     newterm = this.replaceIf(newterm);
378     // Exponentiations-Problem x^y -> Math(exp(x,y).
379     newterm = this.replacePow(newterm);
380     newterm = this.replaceIdByObj(newterm);
381     for (i=0; i<from.length; i++) {
382         expr = new RegExp(['(\\W|^)(',from[i],')'].join(''),"ig");  // sin -> Math.sin and asin -> Math.asin 
383         newterm = newterm.replace(expr,['$1',to[i]].join(''));
384     } 
385     newterm = newterm.replace(/True/g,'true');
386     newterm = newterm.replace(/False/g,'false');
387     newterm = newterm.replace(/fasle/g,'false');
388 
389     newterm = newterm.replace(/Pi/g,'Math.PI');
390     return newterm;
391 };
392 
393 /**
394  * Finds dependencies in a given term and resolves them by adding the
395  * dependent object to the found objects child elements.
396  * @param {JXG.GeometryElement} me Object depending on objects in given term.
397  * @param {String} term String containing dependencies for the given object.
398  * @param {JXG.Board} [board=me.board] Reference to a board
399  */
400 JXG.GeonextParser.findDependencies = function(me, term, board) {
401     if(typeof board=='undefined')
402         board = me.board;
403 
404     var elements = board.elementsByName,
405         el, expr, elmask;
406 
407     for (el in elements) {
408         if (el != me.name) {
409             if(elements[el].type == JXG.OBJECT_TYPE_TEXT) {
410                 if(!elements[el].isLabel) {
411                     elmask = el.replace(/\[/g,'\\[');
412                     elmask = elmask.replace(/\]/g,'\\]');
413                     expr = new RegExp("\\(\(\[\\w\\[\\]'_ \]+,\)*\("+elmask+"\)\(,\[\\w\\[\\]'_ \]+\)*\\)","g");  // Searches (A), (A,B),(A,B,C)
414                     if (term.search(expr)>=0) {
415                         elements[el].addChild(me);
416                     }
417                 }
418             }
419             else {
420                 elmask = el.replace(/\[/g,'\\[');
421                 elmask = elmask.replace(/\]/g,'\\]');
422                 expr = new RegExp("\\(\(\[\\w\\[\\]'_ \]+,\)*\("+elmask+"\)\(,\[\\w\\[\\]'_ \]+\)*\\)","g");  // Searches (A), (A,B),(A,B,C)
423                 if (term.search(expr)>=0) {
424                     elements[el].addChild(me);
425                 }
426             }
427         }
428     }
429 };
430 
431