      1 /*
      2     Copyright 2008-2015
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true, html_sanitize: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /* depends:
     37  jxg
     38  base/constants
     39  */
     40
     41 /**
     42  * @fileoverview type.js contains several functions to help deal with javascript's weak types. This file mainly consists
     43  * of detector functions which verify if a variable is or is not of a specific type and converter functions that convert
     44  * variables to another type or normalize the type of a variable.
     45  */
     46
     47 define([
     48     'jxg', 'base/constants'
     49 ], function (JXG, Const) {
     50
     51     "use strict";
     52
     53     JXG.extend(JXG, /** @lends JXG */ {
     54         /**
     55          * Checks if the given string is an id within the given board.
     56          * @param {JXG.Board} board
     57          * @param {String} s
     58          * @returns {Boolean}
     59          */
     60         isId: function (board, s) {
     61             return typeof s === 'string' && !!board.objects[s];
     62         },
     63
     64         /**
     65          * Checks if the given string is a name within the given board.
     66          * @param {JXG.Board} board
     67          * @param {String} s
     68          * @returns {Boolean}
     69          */
     70         isName: function (board, s) {
     71             return typeof s === 'string' && !!board.elementsByName[s];
     72         },
     73
     74         /**
     75          * Checks if the given string is a group id within the given board.
     76          * @param {JXG.Board} board
     77          * @param {String} s
     78          * @returns {Boolean}
     79          */
     80         isGroup: function (board, s) {
     81             return typeof s === 'string' && !!board.groups[s];
     82         },
     83
     84         /**
     85          * Checks if the value of a given variable is of type string.
     86          * @param v A variable of any type.
     87          * @returns {Boolean} True, if v is of type string.
     88          */
     89         isString: function (v) {
     90             return typeof v === "string";
     91         },
     92
     93         /**
     94          * Checks if the value of a given variable is of type number.
     95          * @param v A variable of any type.
     96          * @returns {Boolean} True, if v is of type number.
     97          */
     98         isNumber: function (v) {
     99             return typeof v === "number" || Object.prototype.toString.call(v) === '[Object Number]';
    100         },
    101
    102         /**
    103          * Checks if a given variable references a function.
    104          * @param v A variable of any type.
    105          * @returns {Boolean} True, if v is a function.
    106          */
    107         isFunction: function (v) {
    108             return typeof v === "function";
    109         },
    110
    111         /**
    112          * Checks if a given variable references an array.
    113          * @param v A variable of any type.
    114          * @returns {Boolean} True, if v is of type array.
    115          */
    116         isArray: function (v) {
    117             var r;
    118
    119             // use the ES5 isArray() method and if that doesn't exist use a fallback.
    120             if (Array.isArray) {
    121                 r = Array.isArray(v);
    122             } else {
    123                 r = (v !== null && typeof v === "object" && typeof v.splice === 'function' && typeof v.join === 'function');
    124             }
    125
    126             return r;
    127         },
    128
    129         /**
    130          * Tests if the input variable is an Object
    131          * @param v
    132          */
    133         isObject: function (v) {
    134             return typeof v === 'object' && !JXG.isArray(v);
    135         },
    136
    137         /**
    138          * Checks if a given variable is a reference of a JSXGraph Point element.
    139          * @param v A variable of any type.
    140          * @returns {Boolean} True, if v is of type JXG.Point.
    141          */
    142         isPoint: function (v) {
    143             if (typeof v === 'object') {
    144                 return (v.elementClass === Const.OBJECT_CLASS_POINT);
    145             }
    146
    147             return false;
    148         },
    149
    150         /**
    151          * Checks if a given variable is a reference of a JSXGraph Point element or an array of length at least two or
    152          * a function returning an array of length two or three.
    153          * @param v A variable of any type.
    154          * @returns {Boolean} True, if v is of type JXG.Point.
    155          */
    156         isPointType: function (v, board) {
    157             var val;
    158
    159             v = board.select(v);
    160             if (this.isArray(v)) {
    161                 return true;
    162             }
    163             if (this.isFunction(v)) {
    164                 val = v();
    165                 if (this.isArray(val) && val.length > 1) {
    166                     return true;
    167                 }
    168             }
    169             return this.isPoint(v);
    170         },
    171
    172         /**
    173          * Checks if a given variable is neither undefined nor null. You should not use this together with global
    174          * variables!
    175          * @param v A variable of any type.
    176          * @returns {Boolean} True, if v is neither undefined nor null.
    177          */
    178         exists: (function (undef) {
    179             return function (v) {
    180                 return !(v === undef || v === null);
    181             };
    182         }()),
    183
    184         /**
    185          * Handle default parameters.
    186          * @param v Given value
    187          * @param d Default value
    188          * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
    189          */
    190         def: function (v, d) {
    191             if (JXG.exists(v)) {
    192                 return v;
    193             }
    194
    195             return d;
    196         },
    197
    198         /**
    199          * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
    200          * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
    201          * @returns {Boolean} String typed boolean value converted to boolean.
    202          */
    203         str2Bool: function (s) {
    204             if (!JXG.exists(s)) {
    205                 return true;
    206             }
    207
    208             if (typeof s === 'boolean') {
    209                 return s;
    210             }
    211
    212             if (JXG.isString(s)) {
    213                 return (s.toLowerCase() === 'true');
    214             }
    215
    216             return false;
    217         },
    218
    219         /**
    220          * Convert a String, a number or a function into a function. This method is used in Transformation.js
    221          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
    222          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
    223          * values is of type string.
    224          * @param {Array} param An array containing strings, numbers, or functions.
    225          * @param {Number} n Length of <tt>param</tt>.
    226          * @returns {Function} A function taking one parameter k which specifies the index of the param element
    227          * to evaluate.
    228          */
    229         createEvalFunction: function (board, param, n) {
    230             var f = [], i, str;
    231
    232             for (i = 0; i < n; i++) {
    233                 f[i] = JXG.createFunction(param[i], board, '', true);
    234             }
    235
    236             return function (k) {
    237                 return f[k]();
    238             };
    239         },
    240
    241         /**
    242          * Convert a String, number or function into a function.
    243          * @param {String|Number|Function} term A variable of type string, function or number.
    244          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
    245          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
    246          * values is of type string.
    247          * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name
    248          * of the variable in a GEONE<sub>X</sub>T string given as term.
    249          * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONE<sub>X</sub>T string.
    250          * @returns {Function} A function evaluation the value given by term or null if term is not of type string,
    251          * function or number.
    252          */
    253         createFunction: function (term, board, variableName, evalGeonext) {
    254             var f = null;
    255
    256             if ((!JXG.exists(evalGeonext) || evalGeonext) && JXG.isString(term)) {
    257                 // Convert GEONExT syntax into  JavaScript syntax
    258                 //newTerm = JXG.GeonextParser.geonext2JS(term, board);
    259                 //return new Function(variableName,'return ' + newTerm + ';');
    260
    261                 //term = JXG.GeonextParser.replaceNameById(term, board);
    262                 //term = JXG.GeonextParser.geonext2JS(term, board);
    263                 f = board.jc.snippet(term, true, variableName, true);
    264             } else if (JXG.isFunction(term)) {
    265                 f = term;
    266             } else if (JXG.isNumber(term)) {
    267                 /** @ignore */
    268                 f = function () {
    269                     return term;
    270                 };
    271             } else if (JXG.isString(term)) {
    272                 // In case of string function like fontsize
    273                 /** @ignore */
    274                 f = function () {
    275                     return term;
    276                 };
    277             }
    278
    279             if (f !== null) {
    280                 f.origin = term;
    281             }
    282
    283             return f;
    284         },
    285
    286         /**
    287          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or function returning coordinate arrays
    288          *  free points with these coordinates are created.
    289          *
    290          * @param {JXG.Board} board Board object
    291          * @param {Array} parents Array containing parent elements for a new object. This array may contain
    292          *    <ul>
    293          *      <li> {@link JXG.Point} objects
    294          *      <li> {@link JXG.Element#name} of {@link JXG.Point} objects
    295          *      <li> {@link JXG.Element#id} of {@link JXG.Point} objects
    296          *      <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3].
    297          *      <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g.
    298          *           [function(){ return 2; }, function(){ return 3; }]
    299          *      <li> Function returning coordinates, e.g. function() { return [2, 3]; }
    300          *    </ul>
    301          *  In the last three cases a new point will be created.
    302          * @param {String} attrClass Main attribute class of newly created points, see {@link JXG@copyAttributes}
    303          * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points.
    304          * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points.
    305          */
    306         providePoints: function (board, parents, attributes, attrClass, attrArray) {
    307             var i, j,
    308                 len,
    309                 lenAttr = 0,
    310                 points = [], attr, p, val;
    311
    312             if (!this.isArray(parents)) {
    313                 parents = [parents];
    314             }
    315             len = parents.length;
    316             if (JXG.exists(attrArray)) {
    317                 lenAttr = attrArray.length;
    318             }
    319             if (lenAttr === 0) {
    320                 attr = this.copyAttributes(attributes, board.options, attrClass);
    321             }
    322
    323             for (i = 0; i < len; ++i) {
    324                 if (lenAttr > 0) {
    325                     j = Math.min(i, lenAttr - 1);
    326                     attr = this.copyAttributes(attributes, board.options, attrClass, attrArray[j]);
    327                 }
    328                 if (this.isArray(parents[i]) && parents[i].length > 1) {
    329                     points.push(board.create('point', parents[i], attr));
    330                 } else if (this.isFunction(parents[i])) {
    331                     val = parents[i]();
    332                     if (this.isArray(val) && (val.length > 1)) {
    333                         points.push(board.create('point', [parents[i]], attr));
    334                     }
    335                 } else {
    336                     points.push(board.select(parents[i]));
    337                 }
    338
    339                 if (!this.isPoint(points[i])) {
    340                     return false;
    341                 }
    342             }
    343
    344             return points;
    345         },
    346
    347         /**
    348          * Generates a function which calls the function fn in the scope of owner.
    349          * @param {Function} fn Function to call.
    350          * @param {Object} owner Scope in which fn is executed.
    351          * @returns {Function} A function with the same signature as fn.
    352          */
    353         bind: function (fn, owner) {
    354             return function () {
    355                 return fn.apply(owner, arguments);
    356             };
    357         },
    358
    359         /**
    360          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
    361          * is just returned.
    362          * @param val Could be anything. Preferably a number or a function.
    363          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
    364          */
    365         evaluate: function (val) {
    366             if (JXG.isFunction(val)) {
    367                 return val();
    368             }
    369
    370             return val;
    371         },
    372
    373         /**
    374          * Search an array for a given value.
    375          * @param {Array} array
    376          * @param value
    377          * @param {String} [sub] Use this property if the elements of the array are objects.
    378          * @returns {Number} The index of the first appearance of the given value, or
    379          * <tt>-1</tt> if the value was not found.
    380          */
    381         indexOf: function (array, value, sub) {
    382             var i, s = JXG.exists(sub);
    383
    384             if (Array.indexOf && !s) {
    385                 return array.indexOf(value);
    386             }
    387
    388             for (i = 0; i < array.length; i++) {
    389                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
    390                     return i;
    391                 }
    392             }
    393
    394             return -1;
    395         },
    396
    397         /**
    398          * Eliminates duplicate entries in an array consisting of numbers and strings.
    399          * @param {Array} a An array of numbers and/or strings.
    400          * @returns {Array} The array with duplicate entries eliminated.
    401          */
    402         eliminateDuplicates: function (a) {
    403             var i,
    404                 len = a.length,
    405                 result = [],
    406                 obj = {};
    407
    408             for (i = 0; i < len; i++) {
    409                 obj[a[i]] = 0;
    410             }
    411
    412             for (i in obj) {
    413                 if (obj.hasOwnProperty(i)) {
    414                     result.push(i);
    415                 }
    416             }
    417
    418             return result;
    419         },
    420
    421         /**
    422          * Swaps to array elements.
    423          * @param {Array} arr
    424          * @param {Number} i
    425          * @param {Number} j
    426          * @returns {Array} Reference to the given array.
    427          */
    428         swap: function (arr, i, j) {
    429             var tmp;
    430
    431             tmp = arr[i];
    432             arr[i] = arr[j];
    433             arr[j] = tmp;
    434
    435             return arr;
    436         },
    437
    438         /**
    439          * Generates a copy of an array and removes the duplicate entries. The original
    440          * Array will be altered.
    441          * @param {Array} arr
    442          * @returns {Array}
    443          */
    444         uniqueArray: function (arr) {
    445             var i, j, isArray, ret = [];
    446
    447             if (arr.length === 0) {
    448                 return [];
    449             }
    450
    451             for (i = 0; i < arr.length; i++) {
    452                 isArray = JXG.isArray(arr[i]);
    453
    454                 for (j = i + 1; j < arr.length; j++) {
    455                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
    456                         arr[i] = [];
    457                     } else if (!isArray && arr[i] === arr[j]) {
    458                         arr[i] = '';
    459                     }
    460                 }
    461             }
    462
    463             j = 0;
    464
    465             for (i = 0; i < arr.length; i++) {
    466                 isArray = JXG.isArray(arr[i]);
    467
    468                 if (!isArray && arr[i] !== '') {
    469                     ret[j] = arr[i];
    470                     j += 1;
    471                 } else if (isArray && arr[i].length !== 0) {
    472                     ret[j] = (arr[i].slice(0));
    473                     j += 1;
    474                 }
    475             }
    476
    477             arr = ret;
    478             return ret;
    479         },
    480
    481         /**
    482          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
    483          * @param {Array} arr
    484          * @param val
    485          * @returns {Boolean}
    486          */
    487         isInArray: function (arr, val) {
    488             return JXG.indexOf(arr, val) > -1;
    489         },
    490
    491         /**
    492          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
    493          * @param {Array} coords
    494          * @param {Boolean} split
    495          * @returns {Array}
    496          */
    497         coordsArrayToMatrix: function (coords, split) {
    498             var i,
    499                 x = [],
    500                 m = [];
    501
    502             for (i = 0; i < coords.length; i++) {
    503                 if (split) {
    504                     x.push(coords[i].usrCoords[1]);
    505                     m.push(coords[i].usrCoords[2]);
    506                 } else {
    507                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
    508                 }
    509             }
    510
    511             if (split) {
    512                 m = [x, m];
    513             }
    514
    515             return m;
    516         },
    517
    518         /**
    519          * Compare two arrays.
    520          * @param {Array} a1
    521          * @param {Array} a2
    522          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
    523          */
    524         cmpArrays: function (a1, a2) {
    525             var i;
    526
    527             // trivial cases
    528             if (a1 === a2) {
    529                 return true;
    530             }
    531
    532             if (a1.length !== a2.length) {
    533                 return false;
    534             }
    535
    536             for (i = 0; i < a1.length; i++) {
    537                 if (a1[i] !== a2[i]) {
    538                     return false;
    539                 }
    540             }
    541
    542             return true;
    543         },
    544
    545         /**
    546          * Removes an element from the given array
    547          * @param {Array} ar
    548          * @param el
    549          * @returns {Array}
    550          */
    551         removeElementFromArray: function (ar, el) {
    552             var i;
    553
    554             for (i = 0; i < ar.length; i++) {
    555                 if (ar[i] === el) {
    556                     ar.splice(i, 1);
    557                     return ar;
    558                 }
    559             }
    560
    561             return ar;
    562         },
    563
    564         /**
    565          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
    566          * @param {Number} n
    567          * @param {Number} p
    568          * @returns {Number}
    569          */
    570         trunc: function (n, p) {
    571             p = JXG.def(p, 0);
    572
    573             /*jslint bitwise: true*/
    574
    575             /*
    576              * The performance gain of this bitwise trick is marginal and the behavior
    577              * is different from toFixed: toFixed rounds, the bitweise operation truncateds
    578              */
    579             //if (p === 0) {
    580             //    n = ~n;
    581             //    n = ~n;
    582             //} else {
    583             n = n.toFixed(p);
    584             //}
    585
    586             return n;
    587         },
    588
    589         /**
    590          * Truncate a number <tt>val</tt> automatically.
    591          * @param val
    592          * @returns {Number}
    593          */
    594         autoDigits: function (val) {
    595             var x = Math.abs(val);
    596
    597             if (x > 0.1) {
    598                 x = val.toFixed(2);
    599             } else if (x >= 0.01) {
    600                 x = val.toFixed(4);
    601             } else if (x >= 0.0001) {
    602                 x = val.toFixed(6);
    603             } else {
    604                 x = val;
    605             }
    606             return x;
    607         },
    608
    609         /**
    610          * Extracts the keys of a given object.
    611          * @param object The object the keys are to be extracted
    612          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
    613          * the object owns itself and not some other object in the prototype chain.
    614          * @returns {Array} All keys of the given object.
    615          */
    616         keys: function (object, onlyOwn) {
    617             var keys = [], property;
    618
    619             // the caller decides if we use hasOwnProperty
    620             /*jslint forin:true*/
    621             for (property in object) {
    622                 if (onlyOwn) {
    623                     if (object.hasOwnProperty(property)) {
    624                         keys.push(property);
    625                     }
    626                 } else {
    627                     keys.push(property);
    628                 }
    629             }
    630             /*jslint forin:false*/
    631
    632             return keys;
    633         },
    634
    635         /**
    636          * This outputs an object with a base class reference to the given object. This is useful if
    637          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
    638          * without changing the original object.
    639          * @param {Object} obj Object to be embedded.
    640          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
    641          */
    642         clone: function (obj) {
    643             var cObj = {};
    644
    645             cObj.prototype = obj;
    646
    647             return cObj;
    648         },
    649
    650         /**
    651          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
    652          * to the new one. Warning: The copied properties of obj2 are just flat copies.
    653          * @param {Object} obj Object to be copied.
    654          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
    655          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
    656          */
    657         cloneAndCopy: function (obj, obj2) {
    658             var r,
    659                 cObj = function () {};
    660
    661             cObj.prototype = obj;
    662
    663             // no hasOwnProperty on purpose
    664             /*jslint forin:true*/
    665             /*jshint forin:true*/
    666
    667             for (r in obj2) {
    668                 cObj[r] = obj2[r];
    669             }
    670
    671             /*jslint forin:false*/
    672             /*jshint forin:false*/
    673
    674
    675             return cObj;
    676         },
    677
    678         /**
    679          * Recursively merges obj2 into obj1. Contrary to {@link JXG#deepCopy} this won't create a new object
    680          * but instead will
    681          * @param {Object} obj1
    682          * @param {Object} obj2
    683          * @returns {Object}
    684          */
    685         merge: function (obj1, obj2) {
    686             var i, j;
    687
    688             for (i in obj2) {
    689                 if (obj2.hasOwnProperty(i)) {
    690                     if (this.isArray(obj2[i])) {
    691                         if (!obj1[i]) {
    692                             obj1[i] = [];
    693                         }
    694
    695                         for (j = 0; j < obj2[i].length; j++) {
    696                             if (typeof obj2[i][j] === 'object') {
    697                                 obj1[i][j] = this.merge(obj1[i][j], obj2[i][j]);
    698                             } else {
    699                                 obj1[i][j] = obj2[i][j];
    700                             }
    701                         }
    702                     } else if (typeof obj2[i] === 'object') {
    703                         if (!obj1[i]) {
    704                             obj1[i] = {};
    705                         }
    706
    707                         obj1[i] = this.merge(obj1[i], obj2[i]);
    708                     } else {
    709                         obj1[i] = obj2[i];
    710                     }
    711                 }
    712             }
    713
    714             return obj1;
    715         },
    716
    717         /**
    718          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
    719          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
    720          * are merged into one object. The properties of the second object have priority.
    721          * @param {Object} obj This object will be copied.
    722          * @param {Object} obj2 This object will merged into the newly created object
    723          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
    724          * @returns {Object} copy of obj or merge of obj and obj2.
    725          */
    726         deepCopy: function (obj, obj2, toLower) {
    727             var c, i, prop, j, i2;
    728
    729             toLower = toLower || false;
    730
    731             if (typeof obj !== 'object' || obj === null) {
    732                 return obj;
    733             }
    734
    735             // missing hasOwnProperty is on purpose in this function
    736             /*jslint forin:true*/
    737             /*jshint forin:false*/
    738
    739             if (this.isArray(obj)) {
    740                 c = [];
    741                 for (i = 0; i < obj.length; i++) {
    742                     prop = obj[i];
    743                     if (typeof prop === 'object') {
    744                         c[i] = this.deepCopy(prop);
    745                     } else {
    746                         c[i] = prop;
    747                     }
    748                 }
    749             } else {
    750                 c = {};
    751                 for (i in obj) {
    752                     i2 = toLower ? i.toLowerCase() : i;
    753
    754                     prop = obj[i];
    755                     if (typeof prop === 'object') {
    756                         c[i2] = this.deepCopy(prop);
    757                     } else {
    758                         c[i2] = prop;
    759                     }
    760                 }
    761
    762                 for (i in obj2) {
    763                     i2 = toLower ? i.toLowerCase() : i;
    764
    765                     prop = obj2[i];
    766                     if (typeof prop === 'object') {
    767                         if (JXG.isArray(prop) || !JXG.exists(c[i2])) {
    768                             c[i2] = this.deepCopy(prop);
    769                         } else {
    770                             c[i2] = this.deepCopy(c[i2], prop, toLower);
    771                         }
    772                     } else {
    773                         c[i2] = prop;
    774                     }
    775                 }
    776             }
    777
    778             /*jslint forin:false*/
    779             /*jshint forin:true*/
    780
    781             return c;
    782         },
    783
    784         /**
    785          * Generates an attributes object that is filled with default values from the Options object
    786          * and overwritten by the user speciified attributes.
    787          * @param {Object} attributes user specified attributes
    788          * @param {Object} options defaults options
    789          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'.
    790          * @returns {Object} The resulting attributes object
    791          */
    792         copyAttributes: function (attributes, options, s) {
    793             var a, i, len, o, isAvail,
    794                 primitives = {
    795                     'circle': 1,
    796                     'curve': 1,
    797                     'image': 1,
    798                     'line': 1,
    799                     'point': 1,
    800                     'polygon': 1,
    801                     'text': 1,
    802                     'ticks': 1,
    803                     'integral': 1
    804                 };
    805
    806
    807             len = arguments.length;
    808             if (len < 3 || primitives[s]) {
    809                 // default options from Options.elements
    810                 a = JXG.deepCopy(options.elements, null, true);
    811             } else {
    812                 a = {};
    813             }
    814
    815             // Only the layer of the main element is set.
    816             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
    817                 a.layer = options.layer[s];
    818             }
    819
    820             // default options from specific elements
    821             o = options;
    822             isAvail = true;
    823             for (i = 2; i < len; i++) {
    824                 if (JXG.exists(o[arguments[i]])) {
    825                     o = o[arguments[i]];
    826                 } else {
    827                     isAvail = false;
    828                     break;
    829                 }
    830             }
    831             if (isAvail) {
    832                 a = JXG.deepCopy(a, o, true);
    833             }
    834
    835             // options from attributes
    836             o = attributes;
    837             isAvail = true;
    838             for (i = 3; i < len; i++) {
    839                 if (JXG.exists(o[arguments[i]])) {
    840                     o = o[arguments[i]];
    841                 } else {
    842                     isAvail = false;
    843                     break;
    844                 }
    845             }
    846             if (isAvail) {
    847                 this.extend(a, o, null, true);
    848             }
    849
    850             // Special treatment of labels
    851             o = options;
    852             isAvail = true;
    853             for (i = 2; i < len; i++) {
    854                 if (JXG.exists(o[arguments[i]])) {
    855                     o = o[arguments[i]];
    856                 } else {
    857                     isAvail = false;
    858                     break;
    859                 }
    860             }
    861             if (isAvail && JXG.exists(o.label)) {
    862                 a.label =  JXG.deepCopy(o.label, a.label);
    863             }
    864             a.label = JXG.deepCopy(options.label, a.label);
    865
    866             return a;
    867         },
    868
    869         /**
    870          * Copy all prototype methods from object "superObject" to object
    871          * "subObject". The constructor of superObject will be available
    872          * in subObject as subObject.constructor[constructorName].
    873          * @param {Object} subObj A JavaScript object which receives new methods.
    874          * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
    875          * @returns {String} constructorName Under this name the constructor of superObj will be available
    876          * in subObject.
    877          * @private
    878          */
    879         copyPrototypeMethods: function (subObject, superObject, constructorName) {
    880             var key;
    881
    882             subObject.prototype[constructorName] = superObject.prototype.constructor;
    883             for (key in superObject.prototype)  {
    884                 subObject.prototype[key] = superObject.prototype[key];
    885             }
    886         },
    887
    888         /**
    889          * Converts a JavaScript object into a JSON string.
    890          * @param {Object} obj A JavaScript object, functions will be ignored.
    891          * @param {Boolean} [noquote=false] No quotes around the name of a property.
    892          * @returns {String} The given object stored in a JSON string.
    893          */
    894         toJSON: function (obj, noquote) {
    895             var list, prop, i, s, val;
    896
    897             noquote = JXG.def(noquote, false);
    898
    899             // check for native JSON support:
    900             if (typeof JSON && JSON.stringify && !noquote) {
    901                 try {
    902                     s = JSON.stringify(obj);
    903                     return s;
    904                 } catch (e) {
    905                     // if something goes wrong, e.g. if obj contains functions we won't return
    906                     // and use our own implementation as a fallback
    907                 }
    908             }
    909
    910             switch (typeof obj) {
    911             case 'object':
    912                 if (obj) {
    913                     list = [];
    914
    915                     if (JXG.isArray(obj)) {
    916                         for (i = 0; i < obj.length; i++) {
    917                             list.push(JXG.toJSON(obj[i], noquote));
    918                         }
    919
    920                         return '[' + list.join(',') + ']';
    921                     }
    922
    923                     for (prop in obj) {
    924                         if (obj.hasOwnProperty(prop)) {
    925                             try {
    926                                 val = JXG.toJSON(obj[prop], noquote);
    927                             } catch (e2) {
    928                                 val = '';
    929                             }
    930
    931                             if (noquote) {
    932                                 list.push(prop + ':' + val);
    933                             } else {
    934                                 list.push('"' + prop + '":' + val);
    935                             }
    936                         }
    937                     }
    938
    939                     return '{' + list.join(',') + '} ';
    940                 }
    941                 return 'null';
    942             case 'string':
    943                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
    944             case 'number':
    945             case 'boolean':
    946                 return obj.toString();
    947             }
    948
    949             return '0';
    950         },
    951
    952         /**
    953          * Resets visPropOld.
    954          * @param {JXG.GeometryElement} el
    955          * @returns {GeometryElement}
    956          */
    957         clearVisPropOld: function (el) {
    958             el.visPropOld = {
    959                 strokecolor: '',
    960                 strokeopacity: '',
    961                 strokewidth: '',
    962                 fillcolor: '',
    963                 fillopacity: '',
    964                 shadow: false,
    965                 firstarrow: false,
    966                 lastarrow: false,
    967                 cssclass: '',
    968                 fontsize: -1,
    969                 left: -100000,
    970                 top: -100000
    971             };
    972
    973             return el;
    974         },
    975
    976         /**
    977          * Checks if an object contains a key, whose value equals to val.
    978          * @param {Object} obj
    979          * @param val
    980          * @returns {Boolean}
    981          */
    982         isInObject: function (obj, val) {
    983             var el;
    984
    985             for (el in obj) {
    986                 if (obj.hasOwnProperty(el)) {
    987                     if (obj[el] === val) {
    988                         return true;
    989                     }
    990                 }
    991             }
    992
    993             return false;
    994         },
    995
    996         /**
    997          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
    998          * @param {String} str
    999          * @returns {String}
    1000          */
    1001         escapeHTML: function (str) {
    1002             return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
    1003         },
    1004
    1005         /**
    1006          * Eliminates all substrings enclosed by < and > and replaces all occurences of
    1007          * &amp; by &, &gt; by >, and &lt; by <.
    1008          * @param {String} str
    1009          * @returns {String}
    1010          */
    1011         unescapeHTML: function (str) {
    1012             // this regex is NOT insecure. We are replacing everything found with ''
    1013             /*jslint regexp:true*/
    1014             return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
    1015         },
    1016
    1017         /**
    1018          * Makes a string lower case except for the first character which will be upper case.
    1019          * @param {String} str Arbitrary string
    1020          * @returns {String} The capitalized string.
    1021          */
    1022         capitalize: function (str) {
    1023             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
    1024         },
    1025
    1026         /**
    1027          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
    1028          * @param {String} str
    1029          * @returns {String}
    1030          */
    1031         trimNumber: function (str) {
    1032             str = str.replace(/^0+/, '');
    1033             str = str.replace(/0+$/, '');
    1034
    1035             if (str[str.length - 1] === '.' || str[str.length - 1] === ',') {
    1036                 str = str.slice(0, -1);
    1037             }
    1038
    1039             if (str[0] === '.' || str[0] === ',') {
    1040                 str = "0" + str;
    1041             }
    1042
    1043             return str;
    1044         },
    1045
    1046         /**
    1047          * Filter an array of elements.
    1048          * @param {Array} list
    1049          * @param {Object|function} filter
    1050          * @returns {Array}
    1051          */
    1052         filterElements: function (list, filter) {
    1053             var i, f, item, flower, value, visPropValue, pass,
    1054                 l = list.length,
    1055                 result = [];
    1056
    1057             if (typeof filter !== 'function' && typeof filter !== 'object') {
    1058                 return result;
    1059             }
    1060
    1061             for (i = 0; i < l; i++) {
    1062                 pass = true;
    1063                 item = list[i];
    1064
    1065                 if (typeof filter === 'object') {
    1066                     for (f in filter) {
    1067                         if (filter.hasOwnProperty(f)) {
    1068                             flower = f.toLowerCase();
    1069
    1070                             if (typeof item[f] === 'function') {
    1071                                 value = item[f]();
    1072                             } else {
    1073                                 value = item[f];
    1074                             }
    1075
    1076                             if (item.visProp && typeof item.visProp[flower] === 'function') {
    1077                                 visPropValue = item.visProp[flower]();
    1078                             } else {
    1079                                 visPropValue = item.visProp && item.visProp[flower];
    1080                             }
    1081
    1082                             if (typeof filter[f] === 'function') {
    1083                                 pass = filter[f](value) || filter[f](visPropValue);
    1084                             } else {
    1085                                 pass = (value === filter[f] || visPropValue === filter[f]);
    1086                             }
    1087
    1088                             if (!pass) {
    1089                                 break;
    1090                             }
    1091                         }
    1092                     }
    1093                 } else if (typeof filter === 'function') {
    1094                     pass = filter(item);
    1095                 }
    1096
    1097                 if (pass) {
    1098                     result.push(item);
    1099                 }
    1100             }
    1101
    1102             return result;
    1103         },
    1104
    1105         /**
    1106          * Remove all leading and trailing whitespaces from a given string.
    1107          * @param {String} str
    1108          * @returns {String}
    1109          */
    1110         trim: function (str) {
    1111             str = str.replace(/^\s+/, '');
    1112             str = str.replace(/\s+$/, '');
    1113
    1114             return str;
    1115         },
    1116
    1117         /**
    1118          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
    1119          * @param {String} str
    1120          * @param {Boolean} caja
    1121          * @returns {String} Sanitized string
    1122          */
    1123         sanitizeHTML: function (str, caja) {
    1124             if (typeof html_sanitize === 'function' && caja) {
    1125                 return html_sanitize(str, function () { return; }, function (id) { return id; });
    1126             }
    1127
    1128             if (str) {
    1129                 str = str.replace(/</g, '<').replace(/>/g, '>');
    1130             }
    1131
    1132             return str;
    1133         },
    1134
    1135         /**
    1136          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
    1137          * @param {*} s
    1138          * @retusn {*} s.Value() if s is an element of type slider, s otherwise
    1139          */
    1140         evalSlider: function (s) {
    1141             if (s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
    1142                 s = s.Value();
    1143             }
    1144
    1145             return s;
    1146         }
    1147     });
    1148
    1149     return JXG;
    1150 });
    1151