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

1238          * @param {String} str
1239          * @param {Boolean} caja
1240          * @returns {String} Sanitized string
1241          */
1242         sanitizeHTML: function (str, caja) {
1243             if (typeof html_sanitize === 'function' && caja) {
1244                 return html_sanitize(str, function () {}, function (id) { return id; });
1245             }
1246 
1247             if (str) {
1248                 str = str.replace(/</g, '<').replace(/>/g, '>');
1249             }
1250 
1251             return str;
1252         },
1253 
1254         /**
1255          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
1256          * @param {*} s
1257          * @returns {*} s.Value() if s is an element of type slider, s otherwise
1258          */
1259         evalSlider: function (s) {
1260             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
1261                 return s.Value();
1262             }
1263 
1264             return s;
1265         }
1266     });
1267 
1268     return JXG;
1269 });
1270