1 /*
  2     Copyright 2008-2023
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Andreas Walter,
  8         Alfred Wassermann,
  9         Peter Wilfahrt
 10 
 11     This file is part of JSXGraph.
 12 
 13     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 14 
 15     You can redistribute it and/or modify it under the terms of the
 16 
 17       * GNU Lesser General Public License as published by
 18         the Free Software Foundation, either version 3 of the License, or
 19         (at your option) any later version
 20       OR
 21       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 22 
 23     JSXGraph is distributed in the hope that it will be useful,
 24     but WITHOUT ANY WARRANTY; without even the implied warranty of
 25     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 26     GNU Lesser General Public License for more details.
 27 
 28     You should have received a copy of the GNU Lesser General Public License and
 29     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 30     and <https://opensource.org/licenses/MIT/>.
 31  */
 32 
 33 /*global JXG: true, define: true, html_sanitize: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /**
 37  * @fileoverview type.js contains several functions to help deal with javascript's weak types.
 38  * This file mainly consists of detector functions which verify if a variable is or is not of
 39  * a specific type and converter functions that convert variables to another type or normalize
 40  * the type of a variable.
 41  */
 42 
 43 import JXG from "../jxg";
 44 import Const from "../base/constants";
 45 
 46 JXG.extend(
 47     JXG,
 48     /** @lends JXG */ {
 49         /**
 50          * Checks if the given object is an JSXGraph board.
 51          * @param {Object} v
 52          * @returns {Boolean}
 53          */
 54         isBoard: function (v) {
 55             return v !== null &&
 56                 typeof v === "object" &&
 57                 this.isNumber(v.BOARD_MODE_NONE) &&
 58                 this.isObject(v.objects) &&
 59                 this.isObject(v.jc) &&
 60                 this.isFunction(v.update) &&
 61                 !!v.containerObj &&
 62                 this.isString(v.id);
 63         },
 64 
 65         /**
 66          * Checks if the given string is an id within the given board.
 67          * @param {JXG.Board} board
 68          * @param {String} s
 69          * @returns {Boolean}
 70          */
 71         isId: function (board, s) {
 72             return typeof s === "string" && !!board.objects[s];
 73         },
 74 
 75         /**
 76          * Checks if the given string is a name within the given board.
 77          * @param {JXG.Board} board
 78          * @param {String} s
 79          * @returns {Boolean}
 80          */
 81         isName: function (board, s) {
 82             return typeof s === "string" && !!board.elementsByName[s];
 83         },
 84 
 85         /**
 86          * Checks if the given string is a group id within the given board.
 87          * @param {JXG.Board} board
 88          * @param {String} s
 89          * @returns {Boolean}
 90          */
 91         isGroup: function (board, s) {
 92             return typeof s === "string" && !!board.groups[s];
 93         },
 94 
 95         /**
 96          * Checks if the value of a given variable is of type string.
 97          * @param v A variable of any type.
 98          * @returns {Boolean} True, if v is of type string.
 99          */
100         isString: function (v) {
101             return typeof v === "string";
102         },
103 
104         /**
105          * Checks if the value of a given variable is of type number.
106          * @param v A variable of any type.
107          * @param {Boolean} [acceptStringNumber=false] If set to true, the function returns true for e.g. v='3.1415'.
108          * @param {Boolean} [acceptNaN=true] If set to false, the function returns false for v=NaN.
109          * @returns {Boolean} True, if v is of type number.
110          */
111         isNumber: function (v, acceptStringNumber, acceptNaN) {
112             var result = (
113                 typeof v === 'number' || Object.prototype.toString.call(v) === '[Object Number]'
114             );
115             acceptStringNumber = acceptStringNumber || false;
116             acceptNaN = acceptNaN === undefined ? true : acceptNaN;
117 
118             if (acceptStringNumber) {
119                 result = result || ('' + parseFloat(v)) === v;
120             }
121             if (!acceptNaN) {
122                 result = result && !isNaN(v);
123             }
124             return result;
125         },
126 
127         /**
128          * Checks if a given variable references a function.
129          * @param v A variable of any type.
130          * @returns {Boolean} True, if v is a function.
131          */
132         isFunction: function (v) {
133             return typeof v === "function";
134         },
135 
136         /**
137          * Checks if a given variable references an array.
138          * @param v A variable of any type.
139          * @returns {Boolean} True, if v is of type array.
140          */
141         isArray: function (v) {
142             var r;
143 
144             // use the ES5 isArray() method and if that doesn't exist use a fallback.
145             if (Array.isArray) {
146                 r = Array.isArray(v);
147             } else {
148                 r =
149                     v !== null &&
150                     typeof v === "object" &&
151                     typeof v.splice === "function" &&
152                     typeof v.join === "function";
153             }
154 
155             return r;
156         },
157 
158         /**
159          * Tests if the input variable is an Object
160          * @param v
161          */
162         isObject: function (v) {
163             return typeof v === "object" && !this.isArray(v);
164         },
165 
166         /**
167          * Tests if the input variable is a DOM Document or DocumentFragment node
168          * @param v A variable of any type
169          */
170         isDocumentOrFragment: function (v) {
171             return this.isObject(v) && (
172                 v.nodeType === 9 || // Node.DOCUMENT_NODE
173                 v.nodeType === 11   // Node.DOCUMENT_FRAGMENT_NODE
174             );
175         },
176 
177         /**
178          * Checks if a given variable is a reference of a JSXGraph Point element.
179          * @param v A variable of any type.
180          * @returns {Boolean} True, if v is of type JXG.Point.
181          */
182         isPoint: function (v) {
183             if (v !== null && typeof v === "object" && this.exists(v.elementClass)) {
184                 return v.elementClass === Const.OBJECT_CLASS_POINT;
185             }
186 
187             return false;
188         },
189 
190         isPoint3D: function (v) {
191             if (v !== null && typeof v === "object" && this.exists(v.elType)) {
192                 return v.elType === "point3d";
193             }
194 
195             return false;
196         },
197 
198         /**
199          * Checks if a given variable is a reference of a JSXGraph Point element or an array of length at least two or
200          * a function returning an array of length two or three.
201          * @param {JXG.Board} board
202          * @param v A variable of any type.
203          * @returns {Boolean} True, if v is of type JXG.Point.
204          */
205         isPointType: function (board, v) {
206             var val, p;
207 
208             if (this.isArray(v)) {
209                 return true;
210             }
211             if (this.isFunction(v)) {
212                 val = v();
213                 if (this.isArray(val) && val.length > 1) {
214                     return true;
215                 }
216             }
217             p = board.select(v);
218             return this.isPoint(p);
219         },
220 
221         /**
222          * Checks if a given variable is a reference of a JSXGraph transformation element or an array
223          * of JSXGraph transformation elements.
224          * @param v A variable of any type.
225          * @returns {Boolean} True, if v is of type JXG.Transformation.
226          */
227         isTransformationOrArray: function (v) {
228             if (v !== null) {
229                 if (this.isArray(v) && v.length > 0) {
230                     return this.isTransformationOrArray(v[0]);
231                 }
232                 if (typeof v === "object") {
233                     return v.type === Const.OBJECT_TYPE_TRANSFORMATION;
234                 }
235             }
236             return false;
237         },
238 
239         /**
240          * Checks if v is an empty object or empty.
241          * @param v {Object|Array}
242          * @returns {boolean} True, if v is an empty object or array.
243          */
244         isEmpty: function (v) {
245             return Object.keys(v).length === 0;
246         },
247 
248         /**
249          * Checks if a given variable is neither undefined nor null. You should not use this together with global
250          * variables!
251          * @param v A variable of any type.
252          * @param {Boolean} [checkEmptyString=false] If set to true, it is also checked whether v is not equal to ''.
253          * @returns {Boolean} True, if v is neither undefined nor null.
254          */
255         exists: function (v, checkEmptyString) {
256             /* eslint-disable eqeqeq */
257             var result = !(v == undefined || v === null);
258             /* eslint-enable eqeqeq */
259             checkEmptyString = checkEmptyString || false;
260 
261             if (checkEmptyString) {
262                 return result && v !== "";
263             }
264             return result;
265         },
266         // exists: (function (undef) {
267         //     return function (v, checkEmptyString) {
268         //         var result = !(v === undef || v === null);
269 
270         //         checkEmptyString = checkEmptyString || false;
271 
272         //         if (checkEmptyString) {
273         //             return result && v !== '';
274         //         }
275         //         return result;
276         //     };
277         // }()),
278 
279         /**
280          * Handle default parameters.
281          * @param v Given value
282          * @param d Default value
283          * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
284          */
285         def: function (v, d) {
286             if (this.exists(v)) {
287                 return v;
288             }
289 
290             return d;
291         },
292 
293         /**
294          * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
295          * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
296          * @returns {Boolean} String typed boolean value converted to boolean.
297          */
298         str2Bool: function (s) {
299             if (!this.exists(s)) {
300                 return true;
301             }
302 
303             if (typeof s === "boolean") {
304                 return s;
305             }
306 
307             if (this.isString(s)) {
308                 return s.toLowerCase() === "true";
309             }
310 
311             return false;
312         },
313 
314         /**
315          * Convert a String, a number or a function into a function. This method is used in Transformation.js
316          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
317          * by a JessieCode string, thus it must be a valid reference only in case one of the param
318          * values is of type string.
319          * @param {Array} param An array containing strings, numbers, or functions.
320          * @param {Number} n Length of <tt>param</tt>.
321          * @returns {Function} A function taking one parameter k which specifies the index of the param element
322          * to evaluate.
323          */
324         createEvalFunction: function (board, param, n) {
325             var f = [], func, i, e,
326                 deps = {};
327 
328             for (i = 0; i < n; i++) {
329                 f[i] = JXG.createFunction(param[i], board);
330                 for (e in f[i].deps) {
331                     deps[e] = f[i].deps;
332                 }
333             }
334 
335             func = function (k) {
336                 return f[k]();
337             };
338             func.deps = deps;
339 
340             return func;
341         },
342 
343         /**
344          * Convert a String, number or function into a function.
345          * @param {String|Number|Function} term A variable of type string, function or number.
346          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
347          * by a JessieCode/GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
348          * values is of type string.
349          * @param {String} variableName Only required if function is supplied as JessieCode string or evalGeonext is set to true.
350          * Describes the variable name of the variable in a JessieCode/GEONE<sub>X</sub>T string given as term.
351          * @param {Boolean} [evalGeonext=false] Obsolete and ignored! Set this true
352          * if term should be treated as a GEONE<sub>X</sub>T string.
353          * @returns {Function} A function evaluating the value given by term or null if term is not of type string,
354          * function or number.
355          */
356         createFunction: function (term, board, variableName, evalGeonext) {
357             var f = null;
358 
359             // if ((!this.exists(evalGeonext) || evalGeonext) && this.isString(term)) {
360             if (this.isString(term)) {
361                 // Convert GEONExT syntax into  JavaScript syntax
362                 //newTerm = JXG.GeonextParser.geonext2JS(term, board);
363                 //return new Function(variableName,'return ' + newTerm + ';');
364                 //term = JXG.GeonextParser.replaceNameById(term, board);
365                 //term = JXG.GeonextParser.geonext2JS(term, board);
366 
367                 f = board.jc.snippet(term, true, variableName, false);
368             } else if (this.isFunction(term)) {
369                 f = term;
370                 f.deps = {};
371             } else if (this.isNumber(term)) {
372                 /** @ignore */
373                 f = function () { return term; };
374                 f.deps = {};
375             // } else if (this.isString(term)) {
376             //     // In case of string function like fontsize
377             //     /** @ignore */
378             //     f = function () { return term; };
379             //     f.deps = {};
380             }
381 
382             if (f !== null) {
383                 f.origin = term;
384             }
385 
386             return f;
387         },
388 
389         /**
390          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
391          *  function returning coordinate arrays
392          *  free points with these coordinates are created.
393          *
394          * @param {JXG.Board} board Board object
395          * @param {Array} parents Array containing parent elements for a new object. This array may contain
396          *    <ul>
397          *      <li> {@link JXG.Point} objects
398          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point} objects
399          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point} objects
400          *      <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3].
401          *      <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g.
402          *           [function(){ return 2; }, function(){ return 3; }]
403          *      <li> Function returning coordinates, e.g. function() { return [2, 3]; }
404          *    </ul>
405          *  In the last three cases a new point will be created.
406          * @param {String} attrClass Main attribute class of newly created points, see {@link JXG#copyAttributes}
407          * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points.
408          * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points.
409          */
410         providePoints: function (board, parents, attributes, attrClass, attrArray) {
411             var i,
412                 j,
413                 len,
414                 lenAttr = 0,
415                 points = [],
416                 attr,
417                 val;
418 
419             if (!this.isArray(parents)) {
420                 parents = [parents];
421             }
422             len = parents.length;
423             if (this.exists(attrArray)) {
424                 lenAttr = attrArray.length;
425             }
426             if (lenAttr === 0) {
427                 attr = this.copyAttributes(attributes, board.options, attrClass);
428             }
429 
430             for (i = 0; i < len; ++i) {
431                 if (lenAttr > 0) {
432                     j = Math.min(i, lenAttr - 1);
433                     attr = this.copyAttributes(
434                         attributes,
435                         board.options,
436                         attrClass,
437                         attrArray[j].toLowerCase()
438                     );
439                 }
440                 if (this.isArray(parents[i]) && parents[i].length > 1) {
441                     points.push(board.create("point", parents[i], attr));
442                     points[points.length - 1]._is_new = true;
443                 } else if (this.isFunction(parents[i])) {
444                     val = parents[i]();
445                     if (this.isArray(val) && val.length > 1) {
446                         points.push(board.create("point", [parents[i]], attr));
447                         points[points.length - 1]._is_new = true;
448                     }
449                 } else {
450                     points.push(board.select(parents[i]));
451                 }
452 
453                 if (!this.isPoint(points[i])) {
454                     return false;
455                 }
456             }
457 
458             return points;
459         },
460 
461         /**
462          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
463          *  function returning coordinate arrays
464          *  free points with these coordinates are created.
465          *
466          * @param {JXG.View3D} view View3D object
467          * @param {Array} parents Array containing parent elements for a new object. This array may contain
468          *    <ul>
469          *      <li> {@link JXG.Point3D} objects
470          *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point3D} objects
471          *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point3D} objects
472          *      <li> Coordinates of 3D points given as array of numbers of length three, e.g. [2, 3, 1].
473          *      <li> Coordinates of 3D points given as array of functions of length three. Each function returns one coordinate, e.g.
474          *           [function(){ return 2; }, function(){ return 3; }, function(){ return 1; }]
475          *      <li> Function returning coordinates, e.g. function() { return [2, 3, 1]; }
476          *    </ul>
477          *  In the last three cases a new 3D point will be created.
478          * @param {String} attrClass Main attribute class of newly created 3D points, see {@link JXG#copyAttributes}
479          * @param {Array} attrArray List of subtype attributes for the newly created 3D points. The list of subtypes is mapped to the list of new 3D points.
480          * @returns {Array} List of newly created {@link JXG.Point3D} elements or false if not all returned elements are 3D points.
481          */
482         providePoints3D: function (view, parents, attributes, attrClass, attrArray) {
483             var i,
484                 j,
485                 len,
486                 lenAttr = 0,
487                 points = [],
488                 attr,
489                 val;
490 
491             if (!this.isArray(parents)) {
492                 parents = [parents];
493             }
494             len = parents.length;
495             if (this.exists(attrArray)) {
496                 lenAttr = attrArray.length;
497             }
498             if (lenAttr === 0) {
499                 attr = this.copyAttributes(attributes, view.board.options, attrClass);
500             }
501 
502             for (i = 0; i < len; ++i) {
503                 if (lenAttr > 0) {
504                     j = Math.min(i, lenAttr - 1);
505                     attr = this.copyAttributes(
506                         attributes,
507                         view.board.options,
508                         attrClass,
509                         attrArray[j]
510                     );
511                 }
512 
513                 if (this.isArray(parents[i]) && parents[i].length > 1) {
514                     points.push(view.create("point3d", parents[i], attr));
515                     points[points.length - 1]._is_new = true;
516                 } else if (this.isFunction(parents[i])) {
517                     val = parents[i]();
518                     if (this.isArray(val) && val.length > 1) {
519                         points.push(view.create("point3d", [parents[i]], attr));
520                         points[points.length - 1]._is_new = true;
521                     }
522                 } else {
523                     points.push(view.select(parents[i]));
524                 }
525 
526                 if (!this.isPoint3D(points[i])) {
527                     return false;
528                 }
529             }
530 
531             return points;
532         },
533 
534         /**
535          * Generates a function which calls the function fn in the scope of owner.
536          * @param {Function} fn Function to call.
537          * @param {Object} owner Scope in which fn is executed.
538          * @returns {Function} A function with the same signature as fn.
539          */
540         bind: function (fn, owner) {
541             return function () {
542                 return fn.apply(owner, arguments);
543             };
544         },
545 
546         /**
547          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
548          * is just returned.
549          * @param val Could be anything. Preferably a number or a function.
550          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
551          */
552         evaluate: function (val) {
553             if (this.isFunction(val)) {
554                 return val();
555             }
556 
557             return val;
558         },
559 
560         /**
561          * Search an array for a given value.
562          * @param {Array} array
563          * @param value
564          * @param {String} [sub] Use this property if the elements of the array are objects.
565          * @returns {Number} The index of the first appearance of the given value, or
566          * <tt>-1</tt> if the value was not found.
567          */
568         indexOf: function (array, value, sub) {
569             var i,
570                 s = this.exists(sub);
571 
572             if (Array.indexOf && !s) {
573                 return array.indexOf(value);
574             }
575 
576             for (i = 0; i < array.length; i++) {
577                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
578                     return i;
579                 }
580             }
581 
582             return -1;
583         },
584 
585         /**
586          * Eliminates duplicate entries in an array consisting of numbers and strings.
587          * @param {Array} a An array of numbers and/or strings.
588          * @returns {Array} The array with duplicate entries eliminated.
589          */
590         eliminateDuplicates: function (a) {
591             var i,
592                 len = a.length,
593                 result = [],
594                 obj = {};
595 
596             for (i = 0; i < len; i++) {
597                 obj[a[i]] = 0;
598             }
599 
600             for (i in obj) {
601                 if (obj.hasOwnProperty(i)) {
602                     result.push(i);
603                 }
604             }
605 
606             return result;
607         },
608 
609         /**
610          * Swaps to array elements.
611          * @param {Array} arr
612          * @param {Number} i
613          * @param {Number} j
614          * @returns {Array} Reference to the given array.
615          */
616         swap: function (arr, i, j) {
617             var tmp;
618 
619             tmp = arr[i];
620             arr[i] = arr[j];
621             arr[j] = tmp;
622 
623             return arr;
624         },
625 
626         /**
627          * Generates a copy of an array and removes the duplicate entries. The original
628          * Array will be altered.
629          * @param {Array} arr
630          * @returns {Array}
631          */
632         uniqueArray: function (arr) {
633             var i,
634                 j,
635                 isArray,
636                 ret = [];
637 
638             if (arr.length === 0) {
639                 return [];
640             }
641 
642             for (i = 0; i < arr.length; i++) {
643                 isArray = this.isArray(arr[i]);
644 
645                 if (!this.exists(arr[i])) {
646                     arr[i] = "";
647                     continue;
648                 }
649                 for (j = i + 1; j < arr.length; j++) {
650                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
651                         arr[i] = [];
652                     } else if (!isArray && arr[i] === arr[j]) {
653                         arr[i] = "";
654                     }
655                 }
656             }
657 
658             j = 0;
659 
660             for (i = 0; i < arr.length; i++) {
661                 isArray = this.isArray(arr[i]);
662 
663                 if (!isArray && arr[i] !== "") {
664                     ret[j] = arr[i];
665                     j++;
666                 } else if (isArray && arr[i].length !== 0) {
667                     ret[j] = arr[i].slice(0);
668                     j++;
669                 }
670             }
671 
672             arr = ret;
673             return ret;
674         },
675 
676         /**
677          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
678          * @param {Array} arr
679          * @param val
680          * @returns {Boolean}
681          */
682         isInArray: function (arr, val) {
683             return JXG.indexOf(arr, val) > -1;
684         },
685 
686         /**
687          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
688          * @param {Array} coords
689          * @param {Boolean} split
690          * @returns {Array}
691          */
692         coordsArrayToMatrix: function (coords, split) {
693             var i,
694                 x = [],
695                 m = [];
696 
697             for (i = 0; i < coords.length; i++) {
698                 if (split) {
699                     x.push(coords[i].usrCoords[1]);
700                     m.push(coords[i].usrCoords[2]);
701                 } else {
702                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
703                 }
704             }
705 
706             if (split) {
707                 m = [x, m];
708             }
709 
710             return m;
711         },
712 
713         /**
714          * Compare two arrays.
715          * @param {Array} a1
716          * @param {Array} a2
717          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
718          */
719         cmpArrays: function (a1, a2) {
720             var i;
721 
722             // trivial cases
723             if (a1 === a2) {
724                 return true;
725             }
726 
727             if (a1.length !== a2.length) {
728                 return false;
729             }
730 
731             for (i = 0; i < a1.length; i++) {
732                 if (this.isArray(a1[i]) && this.isArray(a2[i])) {
733                     if (!this.cmpArrays(a1[i], a2[i])) {
734                         return false;
735                     }
736                 } else if (a1[i] !== a2[i]) {
737                     return false;
738                 }
739             }
740 
741             return true;
742         },
743 
744         /**
745          * Removes an element from the given array
746          * @param {Array} ar
747          * @param el
748          * @returns {Array}
749          */
750         removeElementFromArray: function (ar, el) {
751             var i;
752 
753             for (i = 0; i < ar.length; i++) {
754                 if (ar[i] === el) {
755                     ar.splice(i, 1);
756                     return ar;
757                 }
758             }
759 
760             return ar;
761         },
762 
763         /**
764          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
765          * @param {Number} n
766          * @param {Number} p
767          * @returns {Number}
768          */
769         trunc: function (n, p) {
770             p = JXG.def(p, 0);
771 
772             return this.toFixed(n, p);
773         },
774 
775         /**
776          * Decimal adjustment of a number.
777          * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round
778          *
779          * @param    {String}    type    The type of adjustment.
780          * @param    {Number}    value    The number.
781          * @param    {Number}    exp        The exponent (the 10 logarithm of the adjustment base).
782          * @returns    {Number}            The adjusted value.
783          *
784          * @private
785          */
786         _decimalAdjust: function (type, value, exp) {
787             // If the exp is undefined or zero...
788             if (exp === undefined || +exp === 0) {
789                 return Math[type](value);
790             }
791 
792             value = +value;
793             exp = +exp;
794             // If the value is not a number or the exp is not an integer...
795             if (isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
796                 return NaN;
797             }
798 
799             // Shift
800             value = value.toString().split("e");
801             value = Math[type](+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp)));
802 
803             // Shift back
804             value = value.toString().split("e");
805             return +(value[0] + "e" + (value[1] ? +value[1] + exp : exp));
806         },
807 
808         /**
809          * Round a number to given number of decimal digits.
810          *
811          * Example: JXG._toFixed(3.14159, -2) gives 3.14
812          * @param  {Number} value Number to be rounded
813          * @param  {Number} exp   Number of decimal digits given as negative exponent
814          * @return {Number}       Rounded number.
815          *
816          * @private
817          */
818         _round10: function (value, exp) {
819             return this._decimalAdjust("round", value, exp);
820         },
821 
822         /**
823          * "Floor" a number to given number of decimal digits.
824          *
825          * Example: JXG._toFixed(3.14159, -2) gives 3.14
826          * @param  {Number} value Number to be floored
827          * @param  {Number} exp   Number of decimal digits given as negative exponent
828          * @return {Number}       "Floored" number.
829          *
830          * @private
831          */
832         _floor10: function (value, exp) {
833             return this._decimalAdjust("floor", value, exp);
834         },
835 
836         /**
837          * "Ceil" a number to given number of decimal digits.
838          *
839          * Example: JXG._toFixed(3.14159, -2) gives 3.15
840          * @param  {Number} value Number to be ceiled
841          * @param  {Number} exp   Number of decimal digits given as negative exponent
842          * @return {Number}       "Ceiled" number.
843          *
844          * @private
845          */
846         _ceil10: function (value, exp) {
847             return this._decimalAdjust("ceil", value, exp);
848         },
849 
850         /**
851          * Replacement of the default toFixed() method.
852          * It does a correct rounding (independent of the browser) and
853          * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which
854          * is returned by JavaScript's toFixed()
855          *
856          * @memberOf JXG
857          * @param  {Number} num    Number tp be rounded
858          * @param  {Number} digits Decimal digits
859          * @return {String}        Rounded number is returned as string
860          */
861         toFixed: function (num, digits) {
862             return this._round10(num, -digits).toFixed(digits);
863         },
864 
865         /**
866          * Truncate a number <tt>val</tt> automatically.
867          * @memberOf JXG
868          * @param val
869          * @returns {Number}
870          */
871         autoDigits: function (val) {
872             var x = Math.abs(val),
873                 str;
874 
875             if (x >= 0.1) {
876                 str = this.toFixed(val, 2);
877             } else if (x >= 0.01) {
878                 str = this.toFixed(val, 4);
879             } else if (x >= 0.0001) {
880                 str = this.toFixed(val, 6);
881             } else {
882                 str = val;
883             }
884             return str;
885         },
886 
887         /**
888          * Extracts the keys of a given object.
889          * @param object The object the keys are to be extracted
890          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
891          * the object owns itself and not some other object in the prototype chain.
892          * @returns {Array} All keys of the given object.
893          */
894         keys: function (object, onlyOwn) {
895             var keys = [],
896                 property;
897 
898             // the caller decides if we use hasOwnProperty
899             /*jslint forin:true*/
900             for (property in object) {
901                 if (onlyOwn) {
902                     if (object.hasOwnProperty(property)) {
903                         keys.push(property);
904                     }
905                 } else {
906                     keys.push(property);
907                 }
908             }
909             /*jslint forin:false*/
910 
911             return keys;
912         },
913 
914         /**
915          * This outputs an object with a base class reference to the given object. This is useful if
916          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
917          * without changing the original object.
918          * @param {Object} obj Object to be embedded.
919          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
920          */
921         clone: function (obj) {
922             var cObj = {};
923 
924             cObj.prototype = obj;
925 
926             return cObj;
927         },
928 
929         /**
930          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
931          * to the new one. Warning: The copied properties of obj2 are just flat copies.
932          * @param {Object} obj Object to be copied.
933          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
934          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
935          */
936         cloneAndCopy: function (obj, obj2) {
937             var r,
938                 cObj = function () {
939                     return undefined;
940                 };
941 
942             cObj.prototype = obj;
943 
944             // no hasOwnProperty on purpose
945             /*jslint forin:true*/
946             /*jshint forin:true*/
947 
948             for (r in obj2) {
949                 cObj[r] = obj2[r];
950             }
951 
952             /*jslint forin:false*/
953             /*jshint forin:false*/
954 
955             return cObj;
956         },
957 
958         /**
959          * Recursively merges obj2 into obj1 in-place. Contrary to {@link JXG#deepCopy} this won't create a new object
960          * but instead will overwrite obj1.
961          * <p>
962          * In contrast to method JXG.mergeAttr, merge recurses into any kind of object, e.g. DOM object and JSXGraph objects.
963          * So, please be careful.
964          * @param {Object} obj1
965          * @param {Object} obj2
966          * @returns {Object}
967          * @see JXG#mergeAttr
968          *
969          * @example
970          * JXG.Options = JXG.merge(JXG.Options, {
971          *     board: {
972          *         showNavigation: false,
973          *         showInfobox: true
974          *     },
975          *     point: {
976          *         face: 'o',
977          *         size: 4,
978          *         fillColor: '#eeeeee',
979          *         highlightFillColor: '#eeeeee',
980          *         strokeColor: 'white',
981          *         highlightStrokeColor: 'white',
982          *         showInfobox: 'inherit'
983          *     }
984          * });
985          *
986          * </pre><div id="JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b" class="jxgbox" style="width: 300px; height: 300px;"></div>
987          * <script type="text/javascript">
988          *     (function() {
989          *         var board = JXG.JSXGraph.initBoard('JXGc5bf0f2a-bd5a-4612-97c2-09f17b1bbc6b',
990          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
991          *     JXG.Options = JXG.merge(JXG.Options, {
992          *         board: {
993          *             showNavigation: false,
994          *             showInfobox: true
995          *         },
996          *         point: {
997          *             face: 'o',
998          *             size: 4,
999          *             fillColor: '#eeeeee',
1000          *             highlightFillColor: '#eeeeee',
1001          *             strokeColor: 'white',
1002          *             highlightStrokeColor: 'white',
1003          *             showInfobox: 'inherit'
1004          *         }
1005          *     });
1006          *
1007          *
1008          *     })();
1009          *
1010          * </script><pre>
1011          */
1012         merge: function (obj1, obj2) {
1013             var i, j, o, oo;
1014 
1015             for (i in obj2) {
1016                 if (obj2.hasOwnProperty(i)) {
1017                     o = obj2[i];
1018                     if (this.isArray(o)) {
1019                         if (!obj1[i]) {
1020                             obj1[i] = [];
1021                         }
1022 
1023                         for (j = 0; j < o.length; j++) {
1024                             oo = obj2[i][j];
1025                             if (typeof obj2[i][j] === 'object') {
1026                                 obj1[i][j] = this.merge(obj1[i][j], oo);
1027                             } else {
1028                                 obj1[i][j] = obj2[i][j];
1029                             }
1030                         }
1031                     } else if (typeof o === 'object') {
1032                         if (!obj1[i]) {
1033                             obj1[i] = {};
1034                         }
1035 
1036                         obj1[i] = this.merge(obj1[i], o);
1037                     } else {
1038                         if (typeof obj1 === 'boolean') {
1039                             // This is necessary in the following scenario:
1040                             //   lastArrow == false
1041                             // and call of
1042                             //   setAttribute({lastArrow: {type: 7}})
1043                             obj1 = {};
1044                         }
1045                         obj1[i] = o;
1046                     }
1047                 }
1048             }
1049 
1050             return obj1;
1051         },
1052 
1053         /**
1054          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
1055          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
1056          * are merged into one object. The properties of the second object have priority.
1057          * @param {Object} obj This object will be copied.
1058          * @param {Object} obj2 This object will merged into the newly created object
1059          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
1060          * @returns {Object} copy of obj or merge of obj and obj2.
1061          */
1062         deepCopy: function (obj, obj2, toLower) {
1063             var c, i, prop, i2;
1064 
1065             toLower = toLower || false;
1066             if (typeof obj !== 'object' || obj === null) {
1067                 return obj;
1068             }
1069 
1070             // Missing hasOwnProperty is on purpose in this function
1071             if (this.isArray(obj)) {
1072                 c = [];
1073                 for (i = 0; i < obj.length; i++) {
1074                     prop = obj[i];
1075                     // Attention: typeof null === 'object'
1076                     if (prop !== null && typeof prop === "object") {
1077                         // We certainly do not want to recurse into a JSXGraph object.
1078                         // This would for sure result in an infinite recursion.
1079                         // As alternative we copy the id of the object.
1080                         if (this.exists(prop.board)) {
1081                             c[i] = prop.id;
1082                         } else {
1083                             c[i] = this.deepCopy(prop, {}, toLower);
1084                         }
1085                     } else {
1086                         c[i] = prop;
1087                     }
1088                 }
1089             } else {
1090                 c = {};
1091                 for (i in obj) {
1092                     if (obj.hasOwnProperty(i)) {
1093                         i2 = toLower ? i.toLowerCase() : i;
1094                         prop = obj[i];
1095                         if (prop !== null && typeof prop === "object") {
1096                             if (this.exists(prop.board)) {
1097                                 c[i2] = prop.id;
1098                             } else {
1099                                 c[i2] = this.deepCopy(prop, {}, toLower);
1100                             }
1101                         } else {
1102                             c[i2] = prop;
1103                         }
1104                     }
1105                 }
1106 
1107                 for (i in obj2) {
1108                     if (obj2.hasOwnProperty(i)) {
1109                         i2 = toLower ? i.toLowerCase() : i;
1110 
1111                         prop = obj2[i];
1112                         if (prop !== null && typeof prop === "object") {
1113                             if (this.isArray(prop) || !this.exists(c[i2])) {
1114                                 c[i2] = this.deepCopy(prop, {}, toLower);
1115                             } else {
1116                                 c[i2] = this.deepCopy(c[i2], prop, toLower);
1117                             }
1118                         } else {
1119                             c[i2] = prop;
1120                         }
1121                     }
1122                 }
1123             }
1124 
1125             return c;
1126         },
1127 
1128         /**
1129          * In-place (deep) merging of attributes. Allows attributes like `{shadow: {enabled: true...}}`
1130          * <p>
1131          * In contrast to method JXG.merge, mergeAttr does not recurse into DOM objects and JSXGraph objects. Instead
1132          * handles (pointers) to these objects are used.
1133          *
1134          * @param {Object} attr Object with attributes - usually containing default options
1135          * @param {Object} special Special option values which overwrite (recursively) the default options
1136          * @param {Boolean} [toLower=true] If true the keys are converted to lower case.
1137          *
1138          * @see JXG#merge
1139          *
1140          */
1141         mergeAttr: function (attr, special, toLower) {
1142             var e, e2, o;
1143 
1144             toLower = toLower || true;
1145 
1146             for (e in special) {
1147                 if (special.hasOwnProperty(e)) {
1148                     e2 = (toLower) ? e.toLowerCase(): e;
1149 
1150                     o = special[e];
1151                     if (this.isObject(o) && o !== null &&
1152                         // Do not recurse into a document object or a JSXGraph object
1153                         !this.isDocumentOrFragment(o) && !this.exists(o.board) &&
1154                         // Do not recurse if a string is provided as "new String(...)"
1155                         typeof o.valueOf() !== 'string') {
1156                         if (attr[e2] === undefined || attr[e2] === null || !this.isObject(attr[e2])) {
1157                             // The last test handles the case:
1158                             //   attr.draft = false;
1159                             //   special.draft = { strokewidth: 4}
1160                             attr[e2] = {};
1161                         }
1162                         this.mergeAttr(attr[e2], o, toLower);
1163                     } else {
1164                         // Flat copy
1165                         // This is also used in the cases
1166                         //   attr.shadow = { enabled: true ...}
1167                         //   special.shadow = false;
1168                         // and
1169                         //   special.anchor is a JSXGraph element
1170                         attr[e2] = o;
1171                     }
1172                 }
1173             }
1174         },
1175 
1176         /**
1177          * Generates an attributes object that is filled with default values from the Options object
1178          * and overwritten by the user specified attributes.
1179          * @param {Object} attributes user specified attributes
1180          * @param {Object} options defaults options
1181          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'.
1182          * @returns {Object} The resulting attributes object
1183          */
1184         copyAttributes: function (attributes, options, s) {
1185             var a, i, len, o, isAvail,
1186                 primitives = {
1187                     circle: 1,
1188                     curve: 1,
1189                     foreignobject: 1,
1190                     image: 1,
1191                     line: 1,
1192                     point: 1,
1193                     polygon: 1,
1194                     text: 1,
1195                     ticks: 1,
1196                     integral: 1
1197                 };
1198 
1199             len = arguments.length;
1200             if (len < 3 || primitives[s]) {
1201                 // Default options from Options.elements
1202                 a = JXG.deepCopy(options.elements, null, true);
1203             } else {
1204                 a = {};
1205             }
1206 
1207             // Only the layer of the main element is set.
1208             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
1209                 a.layer = options.layer[s];
1210             }
1211 
1212             // Default options from the specific element like 'line' in
1213             // copyAttribute(attributes, board.options, 'line')
1214             o = options;
1215             isAvail = true;
1216             for (i = 2; i < len; i++) {
1217                 if (this.exists(o[arguments[i]])) {
1218                     o = o[arguments[i]];
1219                 } else {
1220                     isAvail = false;
1221                     break;
1222                 }
1223             }
1224             if (isAvail) {
1225                 a = JXG.deepCopy(a, o, true);
1226             }
1227 
1228             // Merge the specific options given in the parameter 'attributes'
1229             // into the default options.
1230             // Additionally, we step into a subelement of attribute like line.point1 in case it is supplied as in
1231             // copyAttribute(attributes, board.options, 'line', 'point1')
1232             // In this case we would merge attributes.point1 into the global line.point1 attributes.
1233             o = (typeof attributes === 'object') ? attributes : {};
1234             isAvail = true;
1235             for (i = 3; i < len; i++) {
1236                 if (this.exists(o[arguments[i]])) {
1237                     o = o[arguments[i]];
1238                 } else {
1239                     isAvail = false;
1240                     break;
1241                 }
1242             }
1243             if (isAvail) {
1244                 this.mergeAttr(a, o, true);
1245             }
1246 
1247             if (arguments[2] === "board") {
1248                 // For board attributes we are done now.
1249                 return a;
1250             }
1251 
1252             // Special treatment of labels
1253             o = options;
1254             isAvail = true;
1255             for (i = 2; i < len; i++) {
1256                 if (this.exists(o[arguments[i]])) {
1257                     o = o[arguments[i]];
1258                 } else {
1259                     isAvail = false;
1260                     break;
1261                 }
1262             }
1263             if (isAvail && this.exists(o.label)) {
1264                 a.label = JXG.deepCopy(o.label, a.label);
1265             }
1266             a.label = JXG.deepCopy(options.label, a.label);
1267 
1268             return a;
1269         },
1270 
1271         /**
1272          * Copy all prototype methods from object "superObject" to object
1273          * "subObject". The constructor of superObject will be available
1274          * in subObject as subObject.constructor[constructorName].
1275          * @param {Object} subObj A JavaScript object which receives new methods.
1276          * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
1277          * @returns {String} constructorName Under this name the constructor of superObj will be available
1278          * in subObject.
1279          * @private
1280          */
1281         copyPrototypeMethods: function (subObject, superObject, constructorName) {
1282             var key;
1283 
1284             subObject.prototype[constructorName] = superObject.prototype.constructor;
1285             for (key in superObject.prototype) {
1286                 if (superObject.prototype.hasOwnProperty(key)) {
1287                     subObject.prototype[key] = superObject.prototype[key];
1288                 }
1289             }
1290         },
1291 
1292         /**
1293          * Converts a JavaScript object into a JSON string.
1294          * @param {Object} obj A JavaScript object, functions will be ignored.
1295          * @param {Boolean} [noquote=false] No quotes around the name of a property.
1296          * @returns {String} The given object stored in a JSON string.
1297          */
1298         toJSON: function (obj, noquote) {
1299             var list, prop, i, s, val;
1300 
1301             noquote = JXG.def(noquote, false);
1302 
1303             // check for native JSON support:
1304             if (JSON !== undefined && JSON.stringify && !noquote) {
1305                 try {
1306                     s = JSON.stringify(obj);
1307                     return s;
1308                 } catch (e) {
1309                     // if something goes wrong, e.g. if obj contains functions we won't return
1310                     // and use our own implementation as a fallback
1311                 }
1312             }
1313 
1314             switch (typeof obj) {
1315                 case "object":
1316                     if (obj) {
1317                         list = [];
1318 
1319                         if (this.isArray(obj)) {
1320                             for (i = 0; i < obj.length; i++) {
1321                                 list.push(JXG.toJSON(obj[i], noquote));
1322                             }
1323 
1324                             return "[" + list.join(",") + "]";
1325                         }
1326 
1327                         for (prop in obj) {
1328                             if (obj.hasOwnProperty(prop)) {
1329                                 try {
1330                                     val = JXG.toJSON(obj[prop], noquote);
1331                                 } catch (e2) {
1332                                     val = "";
1333                                 }
1334 
1335                                 if (noquote) {
1336                                     list.push(prop + ":" + val);
1337                                 } else {
1338                                     list.push('"' + prop + '":' + val);
1339                                 }
1340                             }
1341                         }
1342 
1343                         return "{" + list.join(",") + "} ";
1344                     }
1345                     return "null";
1346                 case "string":
1347                     return "'" + obj.replace(/(["'])/g, "\\$1") + "'";
1348                 case "number":
1349                 case "boolean":
1350                     return obj.toString();
1351             }
1352 
1353             return "0";
1354         },
1355 
1356         /**
1357          * Resets visPropOld.
1358          * @param {JXG.GeometryElement} el
1359          * @returns {GeometryElement}
1360          */
1361         clearVisPropOld: function (el) {
1362             el.visPropOld = {
1363                 cssclass: "",
1364                 cssdefaultstyle: "",
1365                 cssstyle: "",
1366                 fillcolor: "",
1367                 fillopacity: "",
1368                 firstarrow: false,
1369                 fontsize: -1,
1370                 lastarrow: false,
1371                 left: -100000,
1372                 linecap: "",
1373                 shadow: false,
1374                 strokecolor: "",
1375                 strokeopacity: "",
1376                 strokewidth: "",
1377                 tabindex: -100000,
1378                 transitionduration: 0,
1379                 top: -100000,
1380                 visible: null
1381             };
1382 
1383             return el;
1384         },
1385 
1386         /**
1387          * Checks if an object contains a key, whose value equals to val.
1388          * @param {Object} obj
1389          * @param val
1390          * @returns {Boolean}
1391          */
1392         isInObject: function (obj, val) {
1393             var el;
1394 
1395             for (el in obj) {
1396                 if (obj.hasOwnProperty(el)) {
1397                     if (obj[el] === val) {
1398                         return true;
1399                     }
1400                 }
1401             }
1402 
1403             return false;
1404         },
1405 
1406         /**
1407          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
1408          * @param {String} str
1409          * @returns {String}
1410          */
1411         escapeHTML: function (str) {
1412             return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
1413         },
1414 
1415         /**
1416          * Eliminates all substrings enclosed by < and > and replaces all occurences of
1417          * &amp; by &, &gt; by >, and &lt; by <.
1418          * @param {String} str
1419          * @returns {String}
1420          */
1421         unescapeHTML: function (str) {
1422             // This regex is NOT insecure. We are replacing everything found with ''
1423             /*jslint regexp:true*/
1424             return str
1425                 .replace(/<\/?[^>]+>/gi, "")
1426                 .replace(/&/g, "&")
1427                 .replace(/</g, "<")
1428                 .replace(/>/g, ">");
1429         },
1430 
1431         /**
1432          * Makes a string lower case except for the first character which will be upper case.
1433          * @param {String} str Arbitrary string
1434          * @returns {String} The capitalized string.
1435          */
1436         capitalize: function (str) {
1437             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
1438         },
1439 
1440         /**
1441          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
1442          * @param {String} str
1443          * @returns {String}
1444          */
1445         trimNumber: function (str) {
1446             str = str.replace(/^0+/, "");
1447             str = str.replace(/0+$/, "");
1448 
1449             if (str[str.length - 1] === "." || str[str.length - 1] === ",") {
1450                 str = str.slice(0, -1);
1451             }
1452 
1453             if (str[0] === "." || str[0] === ",") {
1454                 str = "0" + str;
1455             }
1456 
1457             return str;
1458         },
1459 
1460         /**
1461          * Filter an array of elements.
1462          * @param {Array} list
1463          * @param {Object|function} filter
1464          * @returns {Array}
1465          */
1466         filterElements: function (list, filter) {
1467             var i,
1468                 f,
1469                 item,
1470                 flower,
1471                 value,
1472                 visPropValue,
1473                 pass,
1474                 l = list.length,
1475                 result = [];
1476 
1477             if (typeof filter !== "function" && typeof filter !== "object") {
1478                 return result;
1479             }
1480 
1481             for (i = 0; i < l; i++) {
1482                 pass = true;
1483                 item = list[i];
1484 
1485                 if (typeof filter === "object") {
1486                     for (f in filter) {
1487                         if (filter.hasOwnProperty(f)) {
1488                             flower = f.toLowerCase();
1489 
1490                             if (typeof item[f] === "function") {
1491                                 value = item[f]();
1492                             } else {
1493                                 value = item[f];
1494                             }
1495 
1496                             if (item.visProp && typeof item.visProp[flower] === "function") {
1497                                 visPropValue = item.visProp[flower]();
1498                             } else {
1499                                 visPropValue = item.visProp && item.visProp[flower];
1500                             }
1501 
1502                             if (typeof filter[f] === "function") {
1503                                 pass = filter[f](value) || filter[f](visPropValue);
1504                             } else {
1505                                 pass = value === filter[f] || visPropValue === filter[f];
1506                             }
1507 
1508                             if (!pass) {
1509                                 break;
1510                             }
1511                         }
1512                     }
1513                 } else if (typeof filter === "function") {
1514                     pass = filter(item);
1515                 }
1516 
1517                 if (pass) {
1518                     result.push(item);
1519                 }
1520             }
1521 
1522             return result;
1523         },
1524 
1525         /**
1526          * Remove all leading and trailing whitespaces from a given string.
1527          * @param {String} str
1528          * @returns {String}
1529          */
1530         trim: function (str) {
1531             // str = str.replace(/^\s+/, '');
1532             // str = str.replace(/\s+$/, '');
1533             //
1534             // return str;
1535             return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
1536         },
1537 
1538         /**
1539          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
1540          * @param {String} str
1541          * @param {Boolean} caja
1542          * @returns {String} Sanitized string
1543          */
1544         sanitizeHTML: function (str, caja) {
1545             if (typeof html_sanitize === "function" && caja) {
1546                 return html_sanitize(
1547                     str,
1548                     function () {
1549                         return undefined;
1550                     },
1551                     function (id) {
1552                         return id;
1553                     }
1554                 );
1555             }
1556 
1557             if (str && typeof str === "string") {
1558                 str = str.replace(/</g, "<").replace(/>/g, ">");
1559             }
1560 
1561             return str;
1562         },
1563 
1564         /**
1565          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
1566          * @param {*} s
1567          * @returns {*} s.Value() if s is an element of type slider, s otherwise
1568          */
1569         evalSlider: function (s) {
1570             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === "function") {
1571                 return s.Value();
1572             }
1573 
1574             return s;
1575         },
1576 
1577         /**
1578          * Convert a string containing a MAXIMA /STACK expression into a JSXGraph / JessieCode string
1579          * or an array of JSXGraph / JessieCode strings.
1580          *
1581          * @example
1582          * console.log( JXG.stack2jsxgraph("%e**x") );
1583          * // Output:
1584          * //    "EULER**x"
1585          *
1586          * @example
1587          * console.log( JXG.stack2jsxgraph("[%pi*(x**2 - 1), %phi*(x - 1), %gamma*(x+1)]") );
1588          * // Output:
1589          * //    [ "PI*(x**2 - 1)", "1.618033988749895*(x - 1)", "0.5772156649015329*(x+1)" ]
1590          *
1591          * @param {String} str
1592          * @returns String
1593          */
1594         stack2jsxgraph: function(str) {
1595             var t;
1596 
1597             t = str.
1598                 replace(/%pi/g, 'PI').
1599                 replace(/%e/g, 'EULER').
1600                 replace(/%phi/g, '1.618033988749895').
1601                 replace(/%gamma/g, '0.5772156649015329').
1602                 trim();
1603 
1604             // String containing array -> array containing strings
1605             if (t[0] === '[' && t[t.length - 1] === ']') {
1606                 t = t.slice(1, -1).split(/\s*,\s*/);
1607             }
1608 
1609             return t;
1610         }
1611     }
1612 );
1613 
1614 export default JXG;
1615