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

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