      1 /*
      2     Copyright 2008-2017
      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' && !this.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 (v !== null && 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 {JXG.Board} board
    154          * @param v A variable of any type.
    155          * @returns {Boolean} True, if v is of type JXG.Point.
    156          */
    157         isPointType: function (board, v) {
    158             var val;
    159
    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             v = board.select(v);
    170             return this.isPoint(v);
    171         },
    172
    173         /**
    174          * Checks if a given variable is neither undefined nor null. You should not use this together with global
    175          * variables!
    176          * @param v A variable of any type.
    177          * @returns {Boolean} True, if v is neither undefined nor null.
    178          */
    179         exists: (function (undef) {
    180             return function (v) {
    181                 return !(v === undef || v === null);
    182             };
    183         }()),
    184
    185         /**
    186          * Handle default parameters.
    187          * @param v Given value
    188          * @param d Default value
    189          * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
    190          */
    191         def: function (v, d) {
    192             if (this.exists(v)) {
    193                 return v;
    194             }
    195
    196             return d;
    197         },
    198
    199         /**
    200          * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
    201          * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
    202          * @returns {Boolean} String typed boolean value converted to boolean.
    203          */
    204         str2Bool: function (s) {
    205             if (!this.exists(s)) {
    206                 return true;
    207             }
    208
    209             if (typeof s === 'boolean') {
    210                 return s;
    211             }
    212
    213             if (this.isString(s)) {
    214                 return (s.toLowerCase() === 'true');
    215             }
    216
    217             return false;
    218         },
    219
    220         /**
    221          * Convert a String, a number or a function into a function. This method is used in Transformation.js
    222          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
    223          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
    224          * values is of type string.
    225          * @param {Array} param An array containing strings, numbers, or functions.
    226          * @param {Number} n Length of <tt>param</tt>.
    227          * @returns {Function} A function taking one parameter k which specifies the index of the param element
    228          * to evaluate.
    229          */
    230         createEvalFunction: function (board, param, n) {
    231             var f = [], i;
    232
    233             for (i = 0; i < n; i++) {
    234                 f[i] = JXG.createFunction(param[i], board, '', true);
    235             }
    236
    237             return function (k) {
    238                 return f[k]();
    239             };
    240         },
    241
    242         /**
    243          * Convert a String, number or function into a function.
    244          * @param {String|Number|Function} term A variable of type string, function or number.
    245          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
    246          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
    247          * values is of type string.
    248          * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name
    249          * of the variable in a GEONE<sub>X</sub>T string given as term.
    250          * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONE<sub>X</sub>T string.
    251          * @returns {Function} A function evaluation the value given by term or null if term is not of type string,
    252          * function or number.
    253          */
    254         createFunction: function (term, board, variableName, evalGeonext) {
    255             var f = null;
    256
    257             if ((!this.exists(evalGeonext) || evalGeonext) && this.isString(term)) {
    258                 // Convert GEONExT syntax into  JavaScript syntax
    259                 //newTerm = JXG.GeonextParser.geonext2JS(term, board);
    260                 //return new Function(variableName,'return ' + newTerm + ';');
    261
    262                 //term = JXG.GeonextParser.replaceNameById(term, board);
    263                 //term = JXG.GeonextParser.geonext2JS(term, board);
    264                 f = board.jc.snippet(term, true, variableName, true);
    265             } else if (this.isFunction(term)) {
    266                 f = term;
    267             } else if (this.isNumber(term)) {
    268                 /** @ignore */
    269                 f = function () {
    270                     return term;
    271                 };
    272             } else if (this.isString(term)) {
    273                 // In case of string function like fontsize
    274                 /** @ignore */
    275                 f = function () {
    276                     return term;
    277                 };
    278             }
    279
    280             if (f !== null) {
    281                 f.origin = term;
    282             }
    283
    284             return f;
    285         },
    286
    287         /**
    288          *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or function returning coordinate arrays
    289          *  free points with these coordinates are created.
    290          *
    291          * @param {JXG.Board} board Board object
    292          * @param {Array} parents Array containing parent elements for a new object. This array may contain
    293          *    <ul>
    294          *      <li> {@link JXG.Point} objects
    295          *      <li> {@link JXG.Element#name} of {@link JXG.Point} objects
    296          *      <li> {@link JXG.Element#id} of {@link JXG.Point} objects
    297          *      <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3].
    298          *      <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g.
    299          *           [function(){ return 2; }, function(){ return 3; }]
    300          *      <li> Function returning coordinates, e.g. function() { return [2, 3]; }
    301          *    </ul>
    302          *  In the last three cases a new point will be created.
    303          * @param {String} attrClass Main attribute class of newly created points, see {@link JXG@copyAttributes}
    304          * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points.
    305          * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points.
    306          */
    307         providePoints: function (board, parents, attributes, attrClass, attrArray) {
    308             var i, j,
    309                 len,
    310                 lenAttr = 0,
    311                 points = [], attr, val;
    312
    313             if (!this.isArray(parents)) {
    314                 parents = [parents];
    315             }
    316             len = parents.length;
    317             if (this.exists(attrArray)) {
    318                 lenAttr = attrArray.length;
    319             }
    320             if (lenAttr === 0) {
    321                 attr = this.copyAttributes(attributes, board.options, attrClass);
    322             }
    323
    324             for (i = 0; i < len; ++i) {
    325                 if (lenAttr > 0) {
    326                     j = Math.min(i, lenAttr - 1);
    327                     attr = this.copyAttributes(attributes, board.options, attrClass, attrArray[j]);
    328                 }
    329                 if (this.isArray(parents[i]) && parents[i].length > 1) {
    330                     points.push(board.create('point', parents[i], attr));
    331                 } else if (this.isFunction(parents[i])) {
    332                     val = parents[i]();
    333                     if (this.isArray(val) && (val.length > 1)) {
    334                         points.push(board.create('point', [parents[i]], attr));
    335                     }
    336                 } else {
    337                     points.push(board.select(parents[i]));
    338                 }
    339
    340                 if (!this.isPoint(points[i])) {
    341                     return false;
    342                 }
    343             }
    344
    345             return points;
    346         },
    347
    348         /**
    349          * Generates a function which calls the function fn in the scope of owner.
    350          * @param {Function} fn Function to call.
    351          * @param {Object} owner Scope in which fn is executed.
    352          * @returns {Function} A function with the same signature as fn.
    353          */
    354         bind: function (fn, owner) {
    355             return function () {
    356                 return fn.apply(owner, arguments);
    357             };
    358         },
    359
    360         /**
    361          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
    362          * is just returned.
    363          * @param val Could be anything. Preferably a number or a function.
    364          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
    365          */
    366         evaluate: function (val) {
    367             if (this.isFunction(val)) {
    368                 return val();
    369             }
    370
    371             return val;
    372         },
    373
    374         /**
    375          * Search an array for a given value.
    376          * @param {Array} array
    377          * @param value
    378          * @param {String} [sub] Use this property if the elements of the array are objects.
    379          * @returns {Number} The index of the first appearance of the given value, or
    380          * <tt>-1</tt> if the value was not found.
    381          */
    382         indexOf: function (array, value, sub) {
    383             var i, s = this.exists(sub);
    384
    385             if (Array.indexOf && !s) {
    386                 return array.indexOf(value);
    387             }
    388
    389             for (i = 0; i < array.length; i++) {
    390                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
    391                     return i;
    392                 }
    393             }
    394
    395             return -1;
    396         },
    397
    398         /**
    399          * Eliminates duplicate entries in an array consisting of numbers and strings.
    400          * @param {Array} a An array of numbers and/or strings.
    401          * @returns {Array} The array with duplicate entries eliminated.
    402          */
    403         eliminateDuplicates: function (a) {
    404             var i,
    405                 len = a.length,
    406                 result = [],
    407                 obj = {};
    408
    409             for (i = 0; i < len; i++) {
    410                 obj[a[i]] = 0;
    411             }
    412
    413             for (i in obj) {
    414                 if (obj.hasOwnProperty(i)) {
    415                     result.push(i);
    416                 }
    417             }
    418
    419             return result;
    420         },
    421
    422         /**
    423          * Swaps to array elements.
    424          * @param {Array} arr
    425          * @param {Number} i
    426          * @param {Number} j
    427          * @returns {Array} Reference to the given array.
    428          */
    429         swap: function (arr, i, j) {
    430             var tmp;
    431
    432             tmp = arr[i];
    433             arr[i] = arr[j];
    434             arr[j] = tmp;
    435
    436             return arr;
    437         },
    438
    439         /**
    440          * Generates a copy of an array and removes the duplicate entries. The original
    441          * Array will be altered.
    442          * @param {Array} arr
    443          * @returns {Array}
    444          */
    445         uniqueArray: function (arr) {
    446             var i, j, isArray, ret = [];
    447
    448             if (arr.length === 0) {
    449                 return [];
    450             }
    451
    452             for (i = 0; i < arr.length; i++) {
    453                 isArray = this.isArray(arr[i]);
    454
    455                 if (!this.exists(arr[i])) {
    456                     arr[i] = '';
    457                     continue;
    458                 }
    459                 for (j = i + 1; j < arr.length; j++) {
    460                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
    461                         arr[i] = [];
    462                     } else if (!isArray && arr[i] === arr[j]) {
    463                         arr[i] = '';
    464                     }
    465                 }
    466             }
    467
    468             j = 0;
    469
    470             for (i = 0; i < arr.length; i++) {
    471                 isArray = this.isArray(arr[i]);
    472
    473                 if (!isArray && arr[i] !== '') {
    474                     ret[j] = arr[i];
    475                     j++;
    476                 } else if (isArray && arr[i].length !== 0) {
    477                     ret[j] = (arr[i].slice(0));
    478                     j++;
    479                 }
    480             }
    481
    482             arr = ret;
    483             return ret;
    484         },
    485
    486         /**
    487          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
    488          * @param {Array} arr
    489          * @param val
    490          * @returns {Boolean}
    491          */
    492         isInArray: function (arr, val) {
    493             return JXG.indexOf(arr, val) > -1;
    494         },
    495
    496         /**
    497          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
    498          * @param {Array} coords
    499          * @param {Boolean} split
    500          * @returns {Array}
    501          */
    502         coordsArrayToMatrix: function (coords, split) {
    503             var i,
    504                 x = [],
    505                 m = [];
    506
    507             for (i = 0; i < coords.length; i++) {
    508                 if (split) {
    509                     x.push(coords[i].usrCoords[1]);
    510                     m.push(coords[i].usrCoords[2]);
    511                 } else {
    512                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
    513                 }
    514             }
    515
    516             if (split) {
    517                 m = [x, m];
    518             }
    519
    520             return m;
    521         },
    522
    523         /**
    524          * Compare two arrays.
    525          * @param {Array} a1
    526          * @param {Array} a2
    527          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
    528          */
    529         cmpArrays: function (a1, a2) {
    530             var i;
    531
    532             // trivial cases
    533             if (a1 === a2) {
    534                 return true;
    535             }
    536
    537             if (a1.length !== a2.length) {
    538                 return false;
    539             }
    540
    541             for (i = 0; i < a1.length; i++) {
    542                 if (this.isArray(a1[i]) && this.isArray(a2[i])) {
    543                     if (!this.cmpArrays(a1[i], a2[i])) {
    544                         return false;
    545                     }
    546                 }
    547                 else if (a1[i] !== a2[i]) {
    548                     return false;
    549                 }
    550             }
    551
    552             return true;
    553         },
    554
    555         /**
    556          * Removes an element from the given array
    557          * @param {Array} ar
    558          * @param el
    559          * @returns {Array}
    560          */
    561         removeElementFromArray: function (ar, el) {
    562             var i;
    563
    564             for (i = 0; i < ar.length; i++) {
    565                 if (ar[i] === el) {
    566                     ar.splice(i, 1);
    567                     return ar;
    568                 }
    569             }
    570
    571             return ar;
    572         },
    573
    574         /**
    575          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
    576          * @param {Number} n
    577          * @param {Number} p
    578          * @returns {Number}
    579          */
    580         trunc: function (n, p) {
    581             p = JXG.def(p, 0);
    582
    583             return this.toFixed(n, p);
    584         },
    585
    586         /**
    587     	 * Decimal adjustment of a number.
    588     	 * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round
    589     	 *
    590     	 * @param	{String}	type	The type of adjustment.
    591     	 * @param	{Number}	value	The number.
    592     	 * @param	{Number}	exp		The exponent (the 10 logarithm of the adjustment base).
    593     	 * @returns	{Number}			The adjusted value.
    594     	 *
    595     	 * @private
    596     	 */
    597     	_decimalAdjust: function(type, value, exp) {
    598     		// If the exp is undefined or zero...
    599     		if (typeof exp === 'undefined' || +exp === 0) {
    600     			return Math[type](value);
    601     		}
    602
    603     		value = +value;
    604     		exp = +exp;
    605     		// If the value is not a number or the exp is not an integer...
    606     		if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
    607     			return NaN;
    608     		}
    609
    610             // Shift
    611     		value = value.toString().split('e');
    612     		value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
    613
    614     		// Shift back
    615     		value = value.toString().split('e');
    616     		return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
    617     	},
    618
    619         /**
    620          * Round a number to given number of decimal digits.
    621          *
    622          * Example: JXG._toFixed(3.14159, -2) gives 3.14
    623          * @param  {Number} value Number to be rounded
    624          * @param  {Number} exp   Number of decimal digits given as negative exponent
    625          * @return {Number}       Rounded number.
    626          *
    627          * @private
    628          */
    629     	_round10: function(value, exp) {
    630     		return this._decimalAdjust('round', value, exp);
    631 		},
    632
    633         /**
    634          * "Floor" a number to given number of decimal digits.
    635          *
    636          * Example: JXG._toFixed(3.14159, -2) gives 3.14
    637          * @param  {Number} value Number to be floored
    638          * @param  {Number} exp   Number of decimal digits given as negative exponent
    639          * @return {Number}       "Floored" number.
    640          *
    641          * @private
    642          */
    643         _floor10: function(value, exp) {
    644     		return this._decimalAdjust('floor', value, exp);
    645     	},
    646
    647         /**
    648          * "Ceil" a number to given number of decimal digits.
    649          *
    650          * Example: JXG._toFixed(3.14159, -2) gives 3.15
    651          * @param  {Number} value Number to be ceiled
    652          * @param  {Number} exp   Number of decimal digits given as negative exponent
    653          * @return {Number}       "Ceiled" number.
    654          *
    655          * @private
    656          */
    657         _ceil10: function(value, exp) {
    658     		return this._decimalAdjust('ceil', value, exp);
    659     	},
    660
    661         /**
    662          * Replacement of the default toFixed() method.
    663          * It does a correct rounding (independent of the browser) and
    664          * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which
    665          * is returned by JavaScript's toFixed()
    666          *
    667          * @param  {Number} num       Number tp be rounded
    668          * @param  {Number} precision Decimal digits
    669          * @return {String}           Rounded number is returned as string
    670          */
    671         toFixed: function(num, precision) {
    672             return this._round10(num, -precision).toFixed(precision);
    673         },
    674
    675         /**
    676          * Truncate a number <tt>val</tt> automatically.
    677          * @param val
    678          * @returns {Number}
    679          */
    680         autoDigits: function (val) {
    681             var x = Math.abs(val),
    682                 str;
    683
    684             if (x > 0.1) {
    685                 str = this.toFixed(val, 2);
    686             } else if (x >= 0.01) {
    687                 str = this.toFixed(val, 4);
    688             } else if (x >= 0.0001) {
    689                 str = this.toFixed(val, 6);
    690             } else {
    691                 str = val;
    692             }
    693             return str;
    694         },
    695
    696         /**
    697          * Extracts the keys of a given object.
    698          * @param object The object the keys are to be extracted
    699          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
    700          * the object owns itself and not some other object in the prototype chain.
    701          * @returns {Array} All keys of the given object.
    702          */
    703         keys: function (object, onlyOwn) {
    704             var keys = [], property;
    705
    706             // the caller decides if we use hasOwnProperty
    707             /*jslint forin:true*/
    708             for (property in object) {
    709                 if (onlyOwn) {
    710                     if (object.hasOwnProperty(property)) {
    711                         keys.push(property);
    712                     }
    713                 } else {
    714                     keys.push(property);
    715                 }
    716             }
    717             /*jslint forin:false*/
    718
    719             return keys;
    720         },
    721
    722         /**
    723          * This outputs an object with a base class reference to the given object. This is useful if
    724          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
    725          * without changing the original object.
    726          * @param {Object} obj Object to be embedded.
    727          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
    728          */
    729         clone: function (obj) {
    730             var cObj = {};
    731
    732             cObj.prototype = obj;
    733
    734             return cObj;
    735         },
    736
    737         /**
    738          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
    739          * to the new one. Warning: The copied properties of obj2 are just flat copies.
    740          * @param {Object} obj Object to be copied.
    741          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
    742          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
    743          */
    744         cloneAndCopy: function (obj, obj2) {
    745             var r,
    746                 cObj = function () {};
    747
    748             cObj.prototype = obj;
    749
    750             // no hasOwnProperty on purpose
    751             /*jslint forin:true*/
    752             /*jshint forin:true*/
    753
    754             for (r in obj2) {
    755                 cObj[r] = obj2[r];
    756             }
    757
    758             /*jslint forin:false*/
    759             /*jshint forin:false*/
    760
    761             return cObj;
    762         },
    763
    764         /**
    765          * Recursively merges obj2 into obj1. Contrary to {@link JXG#deepCopy} this won't create a new object
    766          * but instead will overwrite obj1.
    767          * @param {Object} obj1
    768          * @param {Object} obj2
    769          * @returns {Object}
    770          */
    771         merge: function (obj1, obj2) {
    772             var i, j;
    773
    774             for (i in obj2) {
    775                 if (obj2.hasOwnProperty(i)) {
    776                     if (this.isArray(obj2[i])) {
    777                         if (!obj1[i]) {
    778                             obj1[i] = [];
    779                         }
    780
    781                         for (j = 0; j < obj2[i].length; j++) {
    782                             if (typeof obj2[i][j] === 'object') {
    783                                 obj1[i][j] = this.merge(obj1[i][j], obj2[i][j]);
    784                             } else {
    785                                 obj1[i][j] = obj2[i][j];
    786                             }
    787                         }
    788                     } else if (typeof obj2[i] === 'object') {
    789                         if (!obj1[i]) {
    790                             obj1[i] = {};
    791                         }
    792
    793                         obj1[i] = this.merge(obj1[i], obj2[i]);
    794                     } else {
    795                         obj1[i] = obj2[i];
    796                     }
    797                 }
    798             }
    799
    800             return obj1;
    801         },
    802
    803         /**
    804          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
    805          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
    806          * are merged into one object. The properties of the second object have priority.
    807          * @param {Object} obj This object will be copied.
    808          * @param {Object} obj2 This object will merged into the newly created object
    809          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
    810          * @returns {Object} copy of obj or merge of obj and obj2.
    811          */
    812         deepCopy: function (obj, obj2, toLower) {
    813             var c, i, prop, i2;
    814
    815             toLower = toLower || false;
    816
    817             if (typeof obj !== 'object' || obj === null) {
    818                 return obj;
    819             }
    820
    821             // missing hasOwnProperty is on purpose in this function
    822             if (this.isArray(obj)) {
    823                 c = [];
    824                 for (i = 0; i < obj.length; i++) {
    825                     prop = obj[i];
    826                     if (typeof prop === 'object') {
    827                         // We certainly do not want to recurse into a JSXGraph object.
    828                         // This would for sure result in an infinite recursion.
    829                         // As alternative we copy the id of the object.
    830                         if (this.exists(prop.board)) {
    831                             c[i] = prop.id;
    832                         } else {
    833                             c[i] = this.deepCopy(prop);
    834                         }
    835                     } else {
    836                         c[i] = prop;
    837                     }
    838                 }
    839             } else {
    840                 c = {};
    841                 for (i in obj) {
    842                     i2 = toLower ? i.toLowerCase() : i;
    843                     prop = obj[i];
    844                     if (prop !== null && typeof prop === 'object') {
    845                         if (this.exists(prop.board)) {
    846                             c[i2] = prop.id;
    847                         } else {
    848                             c[i2] = this.deepCopy(prop);
    849                         }
    850                     } else {
    851                         c[i2] = prop;
    852                     }
    853                 }
    854
    855                 for (i in obj2) {
    856                     i2 = toLower ? i.toLowerCase() : i;
    857
    858                     prop = obj2[i];
    859                     if (typeof prop === 'object') {
    860                         if (this.isArray(prop) || !this.exists(c[i2])) {
    861                             c[i2] = this.deepCopy(prop);
    862                         } else {
    863                             c[i2] = this.deepCopy(c[i2], prop, toLower);
    864                         }
    865                     } else {
    866                         c[i2] = prop;
    867                     }
    868                 }
    869             }
    870
    871             return c;
    872         },
    873
    874         /**
    875          * Generates an attributes object that is filled with default values from the Options object
    876          * and overwritten by the user specified attributes.
    877          * @param {Object} attributes user specified attributes
    878          * @param {Object} options defaults options
    879          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'.
    880          * @returns {Object} The resulting attributes object
    881          */
    882         copyAttributes: function (attributes, options, s) {
    883             var a, i, len, o, isAvail,
    884                 primitives = {
    885                     'circle': 1,
    886                     'curve': 1,
    887                     'image': 1,
    888                     'line': 1,
    889                     'point': 1,
    890                     'polygon': 1,
    891                     'text': 1,
    892                     'ticks': 1,
    893                     'integral': 1
    894                 };
    895
    896
    897             len = arguments.length;
    898             if (len < 3 || primitives[s]) {
    899                 // default options from Options.elements
    900                 a = JXG.deepCopy(options.elements, null, true);
    901             } else {
    902                 a = {};
    903             }
    904
    905             // Only the layer of the main element is set.
    906             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
    907                 a.layer = options.layer[s];
    908             }
    909
    910             // default options from specific elements
    911             o = options;
    912             isAvail = true;
    913             for (i = 2; i < len; i++) {
    914                 if (this.exists(o[arguments[i]])) {
    915                     o = o[arguments[i]];
    916                 } else {
    917                     isAvail = false;
    918                     break;
    919                 }
    920             }
    921             if (isAvail) {
    922                 a = JXG.deepCopy(a, o, true);
    923             }
    924
    925             // options from attributes
    926             o = attributes;
    927             isAvail = true;
    928             for (i = 3; i < len; i++) {
    929                 if (this.exists(o[arguments[i]])) {
    930                     o = o[arguments[i]];
    931                 } else {
    932                     isAvail = false;
    933                     break;
    934                 }
    935             }
    936             if (isAvail) {
    937                 this.extend(a, o, null, true);
    938             }
    939
    940             // Special treatment of labels
    941             o = options;
    942             isAvail = true;
    943             for (i = 2; i < len; i++) {
    944                 if (this.exists(o[arguments[i]])) {
    945                     o = o[arguments[i]];
    946                 } else {
    947                     isAvail = false;
    948                     break;
    949                 }
    950             }
    951             if (isAvail && this.exists(o.label)) {
    952                 a.label =  JXG.deepCopy(o.label, a.label);
    953             }
    954             a.label = JXG.deepCopy(options.label, a.label);
    955
    956             return a;
    957         },
    958
    959         /**
    960          * Copy all prototype methods from object "superObject" to object
    961          * "subObject". The constructor of superObject will be available
    962          * in subObject as subObject.constructor[constructorName].
    963          * @param {Object} subObj A JavaScript object which receives new methods.
    964          * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
    965          * @returns {String} constructorName Under this name the constructor of superObj will be available
    966          * in subObject.
    967          * @private
    968          */
    969         copyPrototypeMethods: function (subObject, superObject, constructorName) {
    970             var key;
    971
    972             subObject.prototype[constructorName] = superObject.prototype.constructor;
    973             for (key in superObject.prototype)  {
    974                 subObject.prototype[key] = superObject.prototype[key];
    975             }
    976         },
    977
    978         /**
    979          * Converts a JavaScript object into a JSON string.
    980          * @param {Object} obj A JavaScript object, functions will be ignored.
    981          * @param {Boolean} [noquote=false] No quotes around the name of a property.
    982          * @returns {String} The given object stored in a JSON string.
    983          */
    984         toJSON: function (obj, noquote) {
    985             var list, prop, i, s, val;
    986
    987             noquote = JXG.def(noquote, false);
    988
    989             // check for native JSON support:
    990             if (typeof JSON && JSON.stringify && !noquote) {
    991                 try {
    992                     s = JSON.stringify(obj);
    993                     return s;
    994                 } catch (e) {
    995                     // if something goes wrong, e.g. if obj contains functions we won't return
    996                     // and use our own implementation as a fallback
    997                 }
    998             }
    999
    1000             switch (typeof obj) {
    1001             case 'object':
    1002                 if (obj) {
    1003                     list = [];
    1004
    1005                     if (this.isArray(obj)) {
    1006                         for (i = 0; i < obj.length; i++) {
    1007                             list.push(JXG.toJSON(obj[i], noquote));
    1008                         }
    1009
    1010                         return '[' + list.join(',') + ']';
    1011                     }
    1012
    1013                     for (prop in obj) {
    1014                         if (obj.hasOwnProperty(prop)) {
    1015                             try {
    1016                                 val = JXG.toJSON(obj[prop], noquote);
    1017                             } catch (e2) {
    1018                                 val = '';
    1019                             }
    1020
    1021                             if (noquote) {
    1022                                 list.push(prop + ':' + val);
    1023                             } else {
    1024                                 list.push('"' + prop + '":' + val);
    1025                             }
    1026                         }
    1027                     }
    1028
    1029                     return '{' + list.join(',') + '} ';
    1030                 }
    1031                 return 'null';
    1032             case 'string':
    1033                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
    1034             case 'number':
    1035             case 'boolean':
    1036                 return obj.toString();
    1037             }
    1038
    1039             return '0';
    1040         },
    1041
    1042         /**
    1043          * Resets visPropOld.
    1044          * @param {JXG.GeometryElement} el
    1045          * @returns {GeometryElement}
    1046          */
    1047         clearVisPropOld: function (el) {
    1048             el.visPropOld = {
    1049                 cssclass: '',
    1050                 cssdefaultstyle: '',
    1051                 cssstyle: '',
    1052                 fillcolor: '',
    1053                 fillopacity: '',
    1054                 firstarrow: false,
    1055                 fontsize: -1,
    1056                 lastarrow: false,
    1057                 left: -100000,
    1058                 linecap: '',
    1059                 shadow: false,
    1060                 strokecolor: '',
    1061                 strokeopacity: '',
    1062                 strokewidth: '',
    1063                 transitionduration: 0,
    1064                 top: -100000,
    1065                 visible: null,
    1066             };
    1067
    1068             return el;
    1069         },
    1070
    1071         /**
    1072          * Checks if an object contains a key, whose value equals to val.
    1073          * @param {Object} obj
    1074          * @param val
    1075          * @returns {Boolean}
    1076          */
    1077         isInObject: function (obj, val) {
    1078             var el;
    1079
    1080             for (el in obj) {
    1081                 if (obj.hasOwnProperty(el)) {
    1082                     if (obj[el] === val) {
    1083                         return true;
    1084                     }
    1085                 }
    1086             }
    1087
    1088             return false;
    1089         },
    1090
    1091         /**
    1092          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
    1093          * @param {String} str
    1094          * @returns {String}
    1095          */
    1096         escapeHTML: function (str) {
    1097             return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
    1098         },
    1099
    1100         /**
    1101          * Eliminates all substrings enclosed by < and > and replaces all occurences of
    1102          * &amp; by &, &gt; by >, and &lt; by <.
    1103          * @param {String} str
    1104          * @returns {String}
    1105          */
    1106         unescapeHTML: function (str) {
    1107             // this regex is NOT insecure. We are replacing everything found with ''
    1108             /*jslint regexp:true*/
    1109             return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
    1110         },
    1111
    1112         /**
    1113          * Makes a string lower case except for the first character which will be upper case.
    1114          * @param {String} str Arbitrary string
    1115          * @returns {String} The capitalized string.
    1116          */
    1117         capitalize: function (str) {
    1118             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
    1119         },
    1120
    1121         /**
    1122          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
    1123          * @param {String} str
    1124          * @returns {String}
    1125          */
    1126         trimNumber: function (str) {
    1127             str = str.replace(/^0+/, '');
    1128             str = str.replace(/0+$/, '');
    1129
    1130             if (str[str.length - 1] === '.' || str[str.length - 1] === ',') {
    1131                 str = str.slice(0, -1);
    1132             }
    1133
    1134             if (str[0] === '.' || str[0] === ',') {
    1135                 str = "0" + str;
    1136             }
    1137
    1138             return str;
    1139         },
    1140
    1141         /**
    1142          * Filter an array of elements.
    1143          * @param {Array} list
    1144          * @param {Object|function} filter
    1145          * @returns {Array}
    1146          */
    1147         filterElements: function (list, filter) {
    1148             var i, f, item, flower, value, visPropValue, pass,
    1149                 l = list.length,
    1150                 result = [];
    1151
    1152             if (typeof filter !== 'function' && typeof filter !== 'object') {
    1153                 return result;
    1154             }
    1155
    1156             for (i = 0; i < l; i++) {
    1157                 pass = true;
    1158                 item = list[i];
    1159
    1160                 if (typeof filter === 'object') {
    1161                     for (f in filter) {
    1162                         if (filter.hasOwnProperty(f)) {
    1163                             flower = f.toLowerCase();
    1164
    1165                             if (typeof item[f] === 'function') {
    1166                                 value = item[f]();
    1167                             } else {
    1168                                 value = item[f];
    1169                             }
    1170
    1171                             if (item.visProp && typeof item.visProp[flower] === 'function') {
    1172                                 visPropValue = item.visProp[flower]();
    1173                             } else {
    1174                                 visPropValue = item.visProp && item.visProp[flower];
    1175                             }
    1176
    1177                             if (typeof filter[f] === 'function') {
    1178                                 pass = filter[f](value) || filter[f](visPropValue);
    1179                             } else {
    1180                                 pass = (value === filter[f] || visPropValue === filter[f]);
    1181                             }
    1182
    1183                             if (!pass) {
    1184                                 break;
    1185                             }
    1186                         }
    1187                     }
    1188                 } else if (typeof filter === 'function') {
    1189                     pass = filter(item);
    1190                 }
    1191
    1192                 if (pass) {
    1193                     result.push(item);
    1194                 }
    1195             }
    1196
    1197             return result;
    1198         },
    1199
    1200         /**
    1201          * Remove all leading and trailing whitespaces from a given string.
    1202          * @param {String} str
    1203          * @returns {String}
    1204          */
    1205         trim: function (str) {
    1206             str = str.replace(/^\s+/, '');
    1207             str = str.replace(/\s+$/, '');
    1208
    1209             return str;
    1210         },
    1211
    1212         /**
    1213          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
    1214          * @param {String} str
    1215          * @param {Boolean} caja
    1216          * @returns {String} Sanitized string
    1217          */
    1218         sanitizeHTML: function (str, caja) {
    1219             if (typeof html_sanitize === 'function' && caja) {
    1220                 return html_sanitize(str, function () {}, function (id) { return id; });
    1221             }
    1222
    1223             if (str) {
    1224                 str = str.replace(/</g, '<').replace(/>/g, '>');
    1225             }
    1226
    1227             return str;
    1228         },
    1229
    1230         /**
    1231          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
    1232          * @param {*} s
    1233          * @returns {*} s.Value() if s is an element of type slider, s otherwise
    1234          */
    1235         evalSlider: function (s) {
    1236             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
    1237                 return s.Value();
    1238             }
    1239
    1240             return s;
    1241         }
    1242     });
    1243
    1244     return JXG;
    1245 });
    1246