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