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