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*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  utils/type
 39  */
 40 
 41 /**
 42  * @fileoverview The JXG.Dump namespace provides methods to save a board to javascript.
 43  */
 44 
 45 define(['jxg', 'utils/type'], function (JXG, Type) {
 46 
 47     "use strict";
 48 
 49     /**
 50      * The JXG.Dump namespace provides classes and methods to save a board to javascript.
 51      * @namespace
 52      */
 53     JXG.Dump = {
 54 
 55         /**
 56          * Adds markers to every element of the board
 57          * @param {JXG.Board} board
 58          * @param {Array|String} markers
 59          * @param {Array} values
 60          */
 61         addMarkers: function (board, markers, values) {
 62             var e, l, i;
 63 
 64             if (!Type.isArray(markers)) {
 65                 markers = [markers];
 66             }
 67 
 68             if (!Type.isArray(values)) {
 69                 values = [values];
 70             }
 71 
 72             l = Math.min(markers.length, values.length);
 73 
 74             markers.length = l;
 75             values.length = l;
 76 
 77             for (e in board.objects) {
 78                 if (board.objects.hasOwnProperty(e)) {
 79                     for (i = 0; i < l; i++) {
 80                         board.objects[e][markers[i]] = values[i];
 81                     }
 82                 }
 83             }
 84         },
 85 
 86         /**
 87          * Removes markers from every element on the board.
 88          * @param {JXG.Board} board
 89          * @param {Array|String} markers
 90          */
 91         deleteMarkers: function (board, markers) {
 92             var e, l, i;
 93 
 94             if (!Type.isArray(markers)) {
 95                 markers = [markers];
 96             }
 97 
 98             l = markers.length;
 99 
100             markers.length = l;
101 
102             for (e in board.objects) {
103                 if (board.objects.hasOwnProperty(e)) {
104                     for (i = 0; i < l; i++) {
105                         delete board.objects[e][markers[i]];
106                     }
107                 }
108             }
109         },
110 
111         /**
112          * Stringifies a string, i.e. puts some quotation marks around <tt>s</tt> if it is of type string.
113          * @param {*} s
114          * @returns {String} " + s + "
115          */
116         str: function (s) {
117             if (typeof s === 'string' && s.substr(0, 7) !== 'function') {
118                 s = '\'' + s + '\'';
119             }
120 
121             return s;
122         },
123 
124         /**
125          * Eliminate default values given by {@link JXG.Options} from the attributes object.
126          * @param {Object} instance Attribute object of the element
127          * @param {Object} s Arbitrary number of objects <tt>instance</tt> will be compared to. Usually these are
128          * sub-objects of the {@link JXG.Board#options} structure.
129          * @returns {Object} Minimal attributes object
130          */
131         minimizeObject: function (instance, s) {
132             var p, pl, i,
133                 def = {},
134                 copy = Type.deepCopy(instance),
135                 defaults = [];
136 
137             for (i = 1; i < arguments.length; i++) {
138                 defaults.push(arguments[i]);
139             }
140 
141             for (i = defaults.length; i > 0; i--) {
142                 def = Type.deepCopy(def, defaults[i - 1], true);
143             }
144 
145             for (p in def) {
146                 if (def.hasOwnProperty(p)) {
147                     pl = p.toLowerCase();
148 
149                     if (typeof def[p] !== 'object' && def[p] === copy[pl]) {
150                         delete copy[pl];
151                     }
152                 }
153             }
154 
155             return copy;
156         },
157 
158         /**
159          * Prepare the attributes object for an element.
160          * @param {JXG.Board} board
161          * @param {JXG.GeometryElement} obj Geometry element which attributes object is generated
162          * @returns {Object} An attributes object.
163          */
164         prepareAttributes: function (board, obj) {
165             var a, s;
166 
167             a = this.minimizeObject(obj.getAttributes(), JXG.Options[obj.elType]);
168 
169             for (s in obj.subs) {
170                 if (obj.subs.hasOwnProperty(s)) {
171                     a[s] = this.minimizeObject(obj.subs[s].getAttributes(), JXG.Options[obj.elType][s], JXG.Options[obj.subs[s].elType]);
172                     a[s].id = obj.subs[s].id;
173                     a[s].name = obj.subs[s].name;
174                 }
175             }
176 
177             a.id = obj.id;
178             a.name = obj.name;
179 
180             return a;
181         },
182 
183         /**
184          * Generate a save-able structure with all elements. This is used by {@link JXG.Dump#toJessie} and {@link JXG.Dump#toJavaScript}
185          * to generate the script.
186          * @param {JXG.Board} board
187          * @returns {Array} An array with all metadata necessary to save the construction.
188          */
189         dump: function (board) {
190             var e, obj, element, s,
191                 props = [],
192                 methods = [],
193                 elementList = [],
194                 len = board.objectsList.length;
195 
196             this.addMarkers(board, 'dumped', false);
197 
198             methods.push({
199                 obj: '$board',
200                 method: 'setBoundingBox',
201                 params: [board.getBoundingBox(), true]
202             });
203 
204             for (e = 0; e < len; e++) {
205                 obj = board.objectsList[e];
206                 element = {};
207 
208                 if (!obj.dumped && obj.dump) {
209                     element.type = obj.getType();
210                     element.parents = obj.getParents();
211 
212                     if (element.type === 'point' && element.parents[0] === 1) {
213                         element.parents = element.parents.slice(1);
214                     }
215 
216                     for (s = 0; s < element.parents.length; s++) {
217                         if (typeof element.parents[s] === 'string') {
218                             element.parents[s] = '\'' + element.parents[s] + '\'';
219                         }
220                     }
221 
222                     element.attributes = this.prepareAttributes(board, obj);
223                     if (element.type === 'glider' && obj.onPolygon) {
224                         props.push({
225                             obj: obj.id,
226                             prop: 'onPolygon',
227                             val: true
228                         });
229                     }
230 
231                     elementList.push(element);
232                 }
233             }
234 
235             this.deleteMarkers(board, 'dumped');
236 
237             return {
238                 elements: elementList,
239                 props: props,
240                 methods: methods
241             };
242         },
243 
244         /**
245          * Converts an array of different values into a parameter string that can be used by the code generators.
246          * @param {Array} a
247          * @param {function} converter A function that is used to transform the elements of <tt>a</tt>. Usually
248          * {@link JXG.toJSON} or {@link JXG.Dump.toJSAN} are used.
249          * @returns {String}
250          */
251         arrayToParamStr: function (a, converter) {
252             var i,
253                 s = [];
254 
255             for (i = 0; i < a.length; i++) {
256                 s.push(converter.call(this, a[i]));
257             }
258 
259             return s.join(', ');
260         },
261 
262         /**
263          * Converts a JavaScript object into a JSAN (JessieScript Attribute Notation) string.
264          * @param {Object} obj A JavaScript object, functions will be ignored.
265          * @returns {String} The given object stored in a JSAN string.
266          */
267         toJSAN: function (obj) {
268             var s, i, list, prop;
269 
270             switch (typeof obj) {
271             case 'object':
272                 if (obj) {
273                     list = [];
274 
275                     if (Type.isArray(obj)) {
276                         for (i = 0; i < obj.length; i++) {
277                             list.push(this.toJSAN(obj[i]));
278                         }
279 
280                         return '[' + list.join(',') + ']';
281                     }
282 
283                     for (prop in obj) {
284                         if (obj.hasOwnProperty(prop)) {
285                             list.push(prop + ': ' + this.toJSAN(obj[prop]));
286                         }
287                     }
288 
289                     return '<<' + list.join(', ') + '>> ';
290                 }
291                 return 'null';
292             case 'string':
293                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
294             case 'number':
295             case 'boolean':
296                 return obj.toString();
297             case 'null':
298                 return 'null';
299             }
300         },
301 
302         /**
303          * Saves the construction in <tt>board</tt> to JessieScript.
304          * @param {JXG.Board} board
305          * @returns {String} JessieScript
306          */
307         toJessie: function (board) {
308             var i, elements,
309                 dump = this.dump(board),
310                 script = [];
311 
312             elements = dump.elements;
313 
314             for (i = 0; i < elements.length; i++) {
315                 if (elements[i].attributes.name.length > 0) {
316                     script.push('// ' + elements[i].attributes.name);
317                 }
318 
319                 script.push('s' + i + ' = ' + elements[i].type + '(' + elements[i].parents.join(', ') + ') ' + this.toJSAN(elements[i].attributes).replace(/\n/, '\\n') + ';');
320                 script.push('');
321             }
322 
323             for (i = 0; i < dump.methods.length; i++) {
324                 script.push(dump.methods[i].obj + '.' + dump.methods[i].method + '(' + this.arrayToParamStr(dump.methods[i].params, this.toJSAN) + ');');
325                 script.push('');
326             }
327 
328             for (i = 0; i < dump.props.length; i++) {
329                 script.push(dump.props[i].obj + '.' + dump.props[i].prop + ' = ' + this.toJSAN(dump.props[i].val) + ';');
330                 script.push('');
331             }
332 
333             return script.join('\n');
334         },
335 
336         /**
337          * Saves the construction in <tt>board</tt> to JavaScript.
338          * @param {JXG.Board} board
339          * @returns {String} JavaScript
340          */
341         toJavaScript: function (board) {
342             var i, elements,
343                 dump = this.dump(board),
344                 script = [];
345 
346             elements = dump.elements;
347 
348             for (i = 0; i < elements.length; i++) {
349                 script.push('board.create("' + elements[i].type + '", [' + elements[i].parents.join(', ') + '], ' + Type.toJSON(elements[i].attributes) + ');');
350             }
351 
352             for (i = 0; i < dump.methods.length; i++) {
353                 script.push(dump.methods[i].obj + '.' + dump.methods[i].method + '(' + this.arrayToParamStr(dump.methods[i].params, Type.toJSON) + ');');
354                 script.push('');
355             }
356 
357             for (i = 0; i < dump.props.length; i++) {
358                 script.push(dump.props[i].obj + '.' + dump.props[i].prop + ' = ' + Type.toJSON(dump.props[i].val) + ';');
359                 script.push('');
360             }
361 
362             return script.join('\n');
363         }
364     };
365 
366     return JXG.Dump;
367 });