      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