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