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

1275          * @param {String} str
1276          * @param {Boolean} caja
1277          * @returns {String} Sanitized string
1278          */
1279         sanitizeHTML: function (str, caja) {
1280             if (typeof html_sanitize === 'function' && caja) {
1281                 return html_sanitize(str, function () { return undefined; }, function (id) { return id; });
1282             }
1283 
1284             if (str) {
1285                 str = str.replace(/</g, '<').replace(/>/g, '>');
1286             }
1287 
1288             return str;
1289         },
1290 
1291         /**
1292          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
1293          * @param {*} s
1294          * @returns {*} s.Value() if s is an element of type slider, s otherwise
1295          */
1296         evalSlider: function (s) {
1297             if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
1298                 return s.Value();
1299             }
1300 
1301             return s;
1302         }
1303     });
1304 
1305     return JXG;
1306 });
1307