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