1 /*
  2     Copyright 2008-2013
  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";
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 neither undefined nor null. You should not use this together with global
152          * variables!
153          * @param v A variable of any type.
154          * @returns {Boolean} True, if v is neither undefined nor null.
155          */
156         exists: (function (undef) {
157             return function (v) {
158                 return !(v === undef || v === null);
159             };
160         }()),
161 
162         /**
163          * Handle default parameters.
164          * @param v Given value
165          * @param d Default value
166          * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
167          */
168         def: function (v, d) {
169             if (JXG.exists(v)) {
170                 return v;
171             }
172 
173             return d;
174         },
175 
176         /**
177          * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
178          * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
179          * @returns {Boolean} String typed boolean value converted to boolean.
180          */
181         str2Bool: function (s) {
182             if (!JXG.exists(s)) {
183                 return true;
184             }
185 
186             if (typeof s === 'boolean') {
187                 return s;
188             }
189 
190             if (JXG.isString(s)) {
191                 return (s.toLowerCase() === 'true');
192             }
193 
194             return false;
195         },
196 
197         /**
198          * Convert a String, a number or a function into a function. This method is used in Transformation.js
199          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
200          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
201          * values is of type string.
202          * @param {Array} param An array containing strings, numbers, or functions.
203          * @param {Number} n Length of <tt>param</tt>.
204          * @returns {Function} A function taking one parameter k which specifies the index of the param element
205          * to evaluate.
206          */
207         createEvalFunction: function (board, param, n) {
208             var f = [], i, str;
209 
210             for (i = 0; i < n; i++) {
211                 f[i] = JXG.createFunction(param[i], board, '', true);
212             }
213 
214             return function (k) {
215                 return f[k]();
216             };
217         },
218 
219         /**
220          * Convert a String, number or function into a function.
221          * @param term A variable of type string, function or number.
222          * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
223          * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
224          * values is of type string.
225          * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name
226          * of the variable in a GEONE<sub>X</sub>T string given as term.
227          * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONE<sub>X</sub>T string.
228          * @returns {Function} A function evaluation the value given by term or null if term is not of type string,
229          * function or number.
230          */
231         createFunction: function (term, board, variableName, evalGeonext) {
232             var f = null;
233 
234             if ((!JXG.exists(evalGeonext) || evalGeonext) && JXG.isString(term)) {
235                 // Convert GEONExT syntax into  JavaScript syntax
236                 //newTerm = JXG.GeonextParser.geonext2JS(term, board);
237                 //return new Function(variableName,'return ' + newTerm + ';');
238 
239                 //term = JXG.GeonextParser.replaceNameById(term, board);
240                 //term = JXG.GeonextParser.geonext2JS(term, board);
241                 f = board.jc.snippet(term, true, variableName, true);
242             } else if (JXG.isFunction(term)) {
243                 f = term;
244             } else if (JXG.isNumber(term)) {
245                 /** @ignore */
246                 f = function () {
247                     return term;
248                 };
249             } else if (JXG.isString(term)) {
250                 // In case of string function like fontsize
251                 /** @ignore */
252                 f = function () {
253                     return term;
254                 };
255             }
256 
257             if (f !== null) {
258                 f.origin = term;
259             }
260 
261             return f;
262         },
263 
264         /**
265          * Generates a function which calls the function fn in the scope of owner.
266          * @param {Function} fn Function to call.
267          * @param {Object} owner Scope in which fn is executed.
268          * @returns {Function} A function with the same signature as fn.
269          */
270         bind: function (fn, owner) {
271             return function () {
272                 return fn.apply(owner, arguments);
273             };
274         },
275 
276         /**
277          * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
278          * is just returned.
279          * @param val Could be anything. Preferably a number or a function.
280          * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
281          */
282         evaluate: function (val) {
283             if (JXG.isFunction(val)) {
284                 return val();
285             }
286 
287             return val;
288         },
289 
290         /**
291          * Search an array for a given value.
292          * @param {Array} array
293          * @param value
294          * @param {String} [sub] Use this property if the elements of the array are objects.
295          * @returns {Number} The index of the first appearance of the given value, or
296          * <tt>-1</tt> if the value was not found.
297          */
298         indexOf: function (array, value, sub) {
299             var i, s = JXG.exists(sub);
300 
301             if (Array.indexOf && !s) {
302                 return array.indexOf(value);
303             }
304 
305             for (i = 0; i < array.length; i++) {
306                 if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
307                     return i;
308                 }
309             }
310 
311             return -1;
312         },
313 
314         /**
315          * Eliminates duplicate entries in an array consisting of numbers and strings.
316          * @param {Array} a An array of numbers and/or strings.
317          * @returns {Array} The array with duplicate entries eliminated.
318          */
319         eliminateDuplicates: function (a) {
320             var i,
321                 len = a.length,
322                 result = [],
323                 obj = {};
324 
325             for (i = 0; i < len; i++) {
326                 obj[a[i]] = 0;
327             }
328 
329             for (i in obj) {
330                 if (obj.hasOwnProperty(i)) {
331                     result.push(i);
332                 }
333             }
334 
335             return result;
336         },
337 
338         /**
339          * Swaps to array elements.
340          * @param {Array} arr
341          * @param {Number} i
342          * @param {Number} j
343          * @returns {Array} Reference to the given array.
344          */
345         swap: function (arr, i, j) {
346             var tmp;
347 
348             tmp = arr[i];
349             arr[i] = arr[j];
350             arr[j] = tmp;
351 
352             return arr;
353         },
354 
355         /**
356          * Generates a copy of an array and removes the duplicate entries. The original
357          * Array will be altered.
358          * @param {Array} arr
359          * @returns {Array}
360          */
361         uniqueArray: function (arr) {
362             var i, j, isArray, ret = [];
363 
364             if (arr.length === 0) {
365                 return [];
366             }
367 
368             for (i = 0; i < arr.length; i++) {
369                 isArray = JXG.isArray(arr[i]);
370 
371                 for (j = i + 1; j < arr.length; j++) {
372                     if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
373                         arr[i] = [];
374                     } else if (!isArray && arr[i] === arr[j]) {
375                         arr[i] = '';
376                     }
377                 }
378             }
379 
380             j = 0;
381 
382             for (i = 0; i < arr.length; i++) {
383                 isArray = JXG.isArray(arr[i]);
384 
385                 if (!isArray && arr[i] !== '') {
386                     ret[j] = arr[i];
387                     j += 1;
388                 } else if (isArray && arr[i].length !== 0) {
389                     ret[j] = (arr[i].slice(0));
390                     j += 1;
391                 }
392             }
393 
394             arr = ret;
395             return ret;
396         },
397 
398         /**
399          * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
400          * @param {Array} arr
401          * @param val
402          * @returns {Boolean}
403          */
404         isInArray: function (arr, val) {
405             return JXG.indexOf(arr, val) > -1;
406         },
407 
408         /**
409          * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
410          * @param {Array} coords
411          * @param {Boolean} split
412          * @returns {Array}
413          */
414         coordsArrayToMatrix: function (coords, split) {
415             var i,
416                 x = [],
417                 m = [];
418 
419             for (i = 0; i < coords.length; i++) {
420                 if (split) {
421                     x.push(coords[i].usrCoords[1]);
422                     m.push(coords[i].usrCoords[2]);
423                 } else {
424                     m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
425                 }
426             }
427 
428             if (split) {
429                 m = [x, m];
430             }
431 
432             return m;
433         },
434 
435         /**
436          * Compare two arrays.
437          * @param {Array} a1
438          * @param {Array} a2
439          * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
440          */
441         cmpArrays: function (a1, a2) {
442             var i;
443 
444             // trivial cases
445             if (a1 === a2) {
446                 return true;
447             }
448 
449             if (a1.length !== a2.length) {
450                 return false;
451             }
452 
453             for (i = 0; i < a1.length; i++) {
454                 if (a1[i] !== a2[i]) {
455                     return false;
456                 }
457             }
458 
459             return true;
460         },
461 
462         /**
463          * Removes an element from the given array
464          * @param {Array} ar
465          * @param el
466          * @returns {Array}
467          */
468         removeElementFromArray: function (ar, el) {
469             var i;
470 
471             for (i = 0; i < ar.length; i++) {
472                 if (ar[i] === el) {
473                     ar.splice(i, 1);
474                     return ar;
475                 }
476             }
477 
478             return ar;
479         },
480 
481         /**
482          * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
483          * @param {Number} n
484          * @param {Number} p
485          * @returns {Number}
486          */
487         trunc: function (n, p) {
488             p = JXG.def(p, 0);
489 
490             /*jslint bitwise: true*/
491 
492             if (p === 0) {
493                 n = ~n;
494                 n = ~n;
495             } else {
496                 n = n.toFixed(p);
497             }
498 
499             return n;
500         },
501 
502         /**
503          * Truncate a number <tt>val</tt> automatically.
504          * @param val
505          * @returns {Number}
506          */
507         autoDigits: function (val) {
508             var x = Math.abs(val);
509 
510             if (x > 0.1) {
511                 x = val.toFixed(2);
512             } else if (x >= 0.01) {
513                 x = val.toFixed(4);
514             } else if (x >= 0.0001) {
515                 x = val.toFixed(6);
516             } else {
517                 x = val;
518             }
519             return x;
520         },
521 
522         /**
523          * Extracts the keys of a given object.
524          * @param object The object the keys are to be extracted
525          * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
526          * the object owns itself and not some other object in the prototype chain.
527          * @returns {Array} All keys of the given object.
528          */
529         keys: function (object, onlyOwn) {
530             var keys = [], property;
531 
532             // the caller decides if we use hasOwnProperty
533             /*jslint forin:true*/
534             for (property in object) {
535                 if (onlyOwn) {
536                     if (object.hasOwnProperty(property)) {
537                         keys.push(property);
538                     }
539                 } else {
540                     keys.push(property);
541                 }
542             }
543             /*jslint forin:false*/
544 
545             return keys;
546         },
547 
548         /**
549          * This outputs an object with a base class reference to the given object. This is useful if
550          * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
551          * without changing the original object.
552          * @param {Object} obj Object to be embedded.
553          * @returns {Object} An object with a base class reference to <tt>obj</tt>.
554          */
555         clone: function (obj) {
556             var cObj = {};
557 
558             cObj.prototype = obj;
559 
560             return cObj;
561         },
562 
563         /**
564          * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
565          * to the new one. Warning: The copied properties of obj2 are just flat copies.
566          * @param {Object} obj Object to be copied.
567          * @param {Object} obj2 Object with data that is to be copied to the new one as well.
568          * @returns {Object} Copy of given object including some new/overwritten data from obj2.
569          */
570         cloneAndCopy: function (obj, obj2) {
571             var r,
572                 cObj = function () {};
573 
574             cObj.prototype = obj;
575 
576             // no hasOwnProperty on purpose
577             /*jslint forin:true*/
578             /*jshint forin:true*/
579 
580             for (r in obj2) {
581                 cObj[r] = obj2[r];
582             }
583 
584             /*jslint forin:false*/
585             /*jshint forin:false*/
586 
587 
588             return cObj;
589         },
590 
591         /**
592          * Recursively merges obj2 into obj1. Contrary to {@link JXG#deepCopy} this won't create a new object
593          * but instead will
594          * @param {Object} obj1
595          * @param {Object} obj2
596          * @returns {Object}
597          */
598         merge: function (obj1, obj2) {
599             var i, j;
600 
601             for (i in obj2) {
602                 if (obj2.hasOwnProperty(i)) {
603                     if (this.isArray(obj2[i])) {
604                         if (!obj1[i]) {
605                             obj1[i] = [];
606                         }
607 
608                         for (j = 0; j < obj2[i].length; j++) {
609                             if (typeof obj2[i][j] === 'object') {
610                                 obj1[i][j] = this.merge(obj1[i][j], obj2[i][j]);
611                             } else {
612                                 obj1[i][j] = obj2[i][j];
613                             }
614                         }
615                     } else if (typeof obj2[i] === 'object') {
616                         if (!obj1[i]) {
617                             obj1[i] = {};
618                         }
619 
620                         obj1[i] = this.merge(obj1[i], obj2[i]);
621                     } else {
622                         obj1[i] = obj2[i];
623                     }
624                 }
625             }
626 
627             return obj1;
628         },
629 
630         /**
631          * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
632          * element-wise instead of just copying the reference. If a second object is supplied, the two objects
633          * are merged into one object. The properties of the second object have priority.
634          * @param {Object} obj This object will be copied.
635          * @param {Object} obj2 This object will merged into the newly created object
636          * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
637          * @returns {Object} copy of obj or merge of obj and obj2.
638          */
639         deepCopy: function (obj, obj2, toLower) {
640             var c, i, prop, j, i2;
641 
642             toLower = toLower || false;
643 
644             if (typeof obj !== 'object' || obj === null) {
645                 return obj;
646             }
647 
648             // missing hasOwnProperty is on purpose in this function
649             /*jslint forin:true*/
650             /*jshint forin:false*/
651 
652             if (this.isArray(obj)) {
653                 c = [];
654                 for (i = 0; i < obj.length; i++) {
655                     prop = obj[i];
656                     if (typeof prop === 'object') {
657                         c[i] = this.deepCopy(prop);
658                     } else {
659                         c[i] = prop;
660                     }
661                 }
662             } else {
663                 c = {};
664                 for (i in obj) {
665                     i2 = toLower ? i.toLowerCase() : i;
666 
667                     prop = obj[i];
668                     if (typeof prop === 'object') {
669                         c[i2] = this.deepCopy(prop);
670                     } else {
671                         c[i2] = prop;
672                     }
673                 }
674 
675                 for (i in obj2) {
676                     i2 = toLower ? i.toLowerCase() : i;
677 
678                     prop = obj2[i];
679                     if (typeof prop === 'object') {
680                         if (JXG.isArray(prop) || !JXG.exists(c[i2])) {
681                             c[i2] = this.deepCopy(prop);
682                         } else {
683                             c[i2] = this.deepCopy(c[i2], prop, toLower);
684                         }
685                     } else {
686                         c[i2] = prop;
687                     }
688                 }
689             }
690 
691             /*jslint forin:false*/
692             /*jshint forin:true*/
693 
694             return c;
695         },
696 
697         /**
698          * Generates an attributes object that is filled with default values from the Options object
699          * and overwritten by the user speciified attributes.
700          * @param {Object} attributes user specified attributes
701          * @param {Object} options defaults options
702          * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'.
703          * @returns {Object} The resulting attributes object
704          */
705         copyAttributes: function (attributes, options, s) {
706             var a, i, len, o, isAvail,
707                 primitives = {
708                     'circle': 1,
709                     'curve': 1,
710                     'image': 1,
711                     'line': 1,
712                     'point': 1,
713                     'polygon': 1,
714                     'text': 1,
715                     'ticks': 1,
716                     'integral': 1
717                 };
718 
719 
720             len = arguments.length;
721             if (len < 3 || primitives[s]) {
722                 // default options from Options.elements
723                 a = JXG.deepCopy(options.elements, null, true);
724             } else {
725                 a = {};
726             }
727 
728             // Only the layer of the main element is set.
729             if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
730                 a.layer = options.layer[s];
731             }
732 
733             // default options from specific elements
734             o = options;
735             isAvail = true;
736             for (i = 2; i < len; i++) {
737                 if (JXG.exists(o[arguments[i]])) {
738                     o = o[arguments[i]];
739                 } else {
740                     isAvail = false;
741                     break;
742                 }
743             }
744             if (isAvail) {
745                 a = JXG.deepCopy(a, o, true);
746             }
747 
748             // options from attributes
749             o = attributes;
750             isAvail = true;
751             for (i = 3; i < len; i++) {
752                 if (JXG.exists(o[arguments[i]])) {
753                     o = o[arguments[i]];
754                 } else {
755                     isAvail = false;
756                     break;
757                 }
758             }
759             if (isAvail) {
760                 this.extend(a, o, null, true);
761             }
762 
763             // Special treatment of labels
764             o = options;
765             isAvail = true;
766             for (i = 2; i < len; i++) {
767                 if (JXG.exists(o[arguments[i]])) {
768                     o = o[arguments[i]];
769                 } else {
770                     isAvail = false;
771                     break;
772                 }
773             }
774             if (isAvail && JXG.exists(o.label)) {
775                 a.label =  JXG.deepCopy(o.label, a.label);
776             }
777             a.label = JXG.deepCopy(options.label, a.label);
778 
779             return a;
780         },
781 
782         /**
783          * Converts a JavaScript object into a JSON string.
784          * @param {Object} obj A JavaScript object, functions will be ignored.
785          * @param {Boolean} [noquote=false] No quotes around the name of a property.
786          * @returns {String} The given object stored in a JSON string.
787          */
788         toJSON: function (obj, noquote) {
789             var list, prop, i, s, val;
790 
791             noquote = JXG.def(noquote, false);
792 
793             // check for native JSON support:
794             if (typeof JSON && JSON.stringify && !noquote) {
795                 try {
796                     s = JSON.stringify(obj);
797                     return s;
798                 } catch (e) {
799                     // if something goes wrong, e.g. if obj contains functions we won't return
800                     // and use our own implementation as a fallback
801                 }
802             }
803 
804             switch (typeof obj) {
805             case 'object':
806                 if (obj) {
807                     list = [];
808 
809                     if (JXG.isArray(obj)) {
810                         for (i = 0; i < obj.length; i++) {
811                             list.push(JXG.toJSON(obj[i], noquote));
812                         }
813 
814                         return '[' + list.join(',') + ']';
815                     }
816 
817                     for (prop in obj) {
818                         if (obj.hasOwnProperty(prop)) {
819                             try {
820                                 val = JXG.toJSON(obj[prop], noquote);
821                             } catch (e2) {
822                                 val = '';
823                             }
824 
825                             if (noquote) {
826                                 list.push(prop + ':' + val);
827                             } else {
828                                 list.push('"' + prop + '":' + val);
829                             }
830                         }
831                     }
832 
833                     return '{' + list.join(',') + '} ';
834                 }
835                 return 'null';
836             case 'string':
837                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
838             case 'number':
839             case 'boolean':
840                 return obj.toString();
841             }
842 
843             return '0';
844         },
845 
846         /**
847          * Resets visPropOld.
848          * @param {JXG.GeometryElement} el
849          * @returns {GeometryElement}
850          */
851         clearVisPropOld: function (el) {
852             el.visPropOld = {
853                 strokecolor: '',
854                 strokeopacity: '',
855                 strokewidth: '',
856                 fillcolor: '',
857                 fillopacity: '',
858                 shadow: false,
859                 firstarrow: false,
860                 lastarrow: false,
861                 cssclass: '',
862                 fontsize: -1,
863                 left: -100000,
864                 right: 100000,
865                 top: -100000
866             };
867 
868             return el;
869         },
870 
871         /**
872          * Checks if an object contains a key, whose value equals to val.
873          * @param {Object} obj
874          * @param val
875          * @returns {Boolean}
876          */
877         isInObject: function (obj, val) {
878             var el;
879 
880             for (el in obj) {
881                 if (obj.hasOwnProperty(el)) {
882                     if (obj[el] === val) {
883                         return true;
884                     }
885                 }
886             }
887 
888             return false;
889         },
890 
891         /**
892          * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
893          * @param {String} str
894          * @returns {String}
895          */
896         escapeHTML: function (str) {
897             return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
898         },
899 
900         /**
901          * Eliminates all substrings enclosed by < and > and replaces all occurences of
902          * &amp; by &, &gt; by >, and &lt; by <.
903          * @param {String} str
904          * @returns {String}
905          */
906         unescapeHTML: function (str) {
907             // this regex is NOT insecure. We are replacing everything found with ''
908             /*jslint regexp:true*/
909             return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
910         },
911 
912         /**
913          * Makes a string lower case except for the first character which will be upper case.
914          * @param {String} str Arbitrary string
915          * @returns {String} The capitalized string.
916          */
917         capitalize: function (str) {
918             return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
919         },
920 
921         /**
922          * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
923          * @param {String} str
924          * @returns {String}
925          */
926         trimNumber: function (str) {
927             str = str.replace(/^0+/, '');
928             str = str.replace(/0+$/, '');
929 
930             if (str[str.length - 1] === '.' || str[str.length - 1] === ',') {
931                 str = str.slice(0, -1);
932             }
933 
934             if (str[0] === '.' || str[0] === ',') {
935                 str = "0" + str;
936             }
937 
938             return str;
939         },
940 
941         /**
942          * Filter an array of elements.
943          * @param {Array} list
944          * @param {Object|function} filter
945          * @returns {Array}
946          */
947         filterElements: function (list, filter) {
948             var i, f, item, flower, value, visPropValue, pass,
949                 l = list.length,
950                 result = [];
951 
952             if (typeof filter !== 'function' && typeof filter !== 'object') {
953                 return result;
954             }
955 
956             for (i = 0; i < l; i++) {
957                 pass = true;
958                 item = list[i];
959 
960                 if (typeof filter === 'object') {
961                     for (f in filter) {
962                         if (filter.hasOwnProperty(f)) {
963                             flower = f.toLowerCase();
964 
965                             if (typeof item[f] === 'function') {
966                                 value = item[f]();
967                             } else {
968                                 value = item[f];
969                             }
970 
971                             if (item.visProp && typeof item.visProp[flower] === 'function') {
972                                 visPropValue = item.visProp[flower]();
973                             } else {
974                                 visPropValue = item.visProp && item.visProp[flower];
975                             }
976 
977                             if (typeof filter[f] === 'function') {
978                                 pass = filter[f](value) || filter[f](visPropValue);
979                             } else {
980                                 pass = (value === filter[f] || visPropValue === filter[f]);
981                             }
982 
983                             if (!pass) {
984                                 break;
985                             }
986                         }
987                     }
988                 } else if (typeof filter === 'function') {
989                     pass = filter(item);
990                 }
991 
992                 if (pass) {
993                     result.push(item);
994                 }
995             }
996 
997             return result;
998         },
999 
1000         /**
1001          * Remove all leading and trailing whitespaces from a given string.
1002          * @param {String} str
1003          * @returns {String}
1004          */
1005         trim: function (str) {
1006             str = str.replace(/^\s+/, '');
1007             str = str.replace(/\s+$/, '');
1008 
1009             return str;
1010         },
1011 
1012         /**
1013          * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
1014          * @param {String} str
1015          * @param {Boolean} caja
1016          * @returns {String} Sanitized string
1017          */
1018         sanitizeHTML: function (str, caja) {
1019             if (typeof html_sanitize === 'function' && caja) {
1020                 return html_sanitize(str, function () { return; }, function (id) { return id; });
1021             }
1022 
1023             if (str) {
1024                 str = str.replace(/</g, '<').replace(/>/g, '>');
1025             }
1026 
1027             return str;
1028         },
1029 
1030         /**
1031          * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
1032          * @param {*} s
1033          * @retusn {*} s.Value() if s is an element of type slider, s otherwise
1034          */
1035         evalSlider: function (s) {
1036             if (s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
1037                 s = s.Value();
1038             }
1039 
1040             return s;
1041         }
1042     });
1043 
1044     return JXG;
1045 });
1046