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