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 to be dumped as JavaScript or JessieCode code.
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(),
172                                                 JXG.Options[obj.elType][s],
173                                                 JXG.Options[obj.subs[s].elType]);
174                     a[s].id = obj.subs[s].id;
175                     a[s].name = obj.subs[s].name;
176                 }
177             }
178 
179             a.id = obj.id;
180             a.name = obj.name;
181 
182             return a;
183         },
184 
185         setBoundingBox: function(methods, board, boardVarName) {
186             methods.push({
187                 obj: boardVarName,
188                 method: 'setBoundingBox',
189                 params: [board.getBoundingBox(), true]
190             });
191 
192             return methods;
193         },
194 
195         /**
196          * Generate a save-able structure with all elements. This is used by {@link JXG.Dump#toJessie} and
197          * {@link JXG.Dump#toJavaScript} to generate the script.
198          * @param {JXG.Board} board
199          * @returns {Array} An array with all metadata necessary to save the construction.
200          */
201         dump: function (board) {
202             var e, obj, element, s,
203                 props = [],
204                 methods = [],
205                 elementList = [],
206                 len = board.objectsList.length;
207 
208             this.addMarkers(board, 'dumped', false);
209 
210             // This has been moved to toJavaScript and toJessie
211             /*
212             methods.push({
213                 obj: '$board',
214                 method: 'setBoundingBox',
215                 params: [board.getBoundingBox(), true]
216             });
217             */
218 
219             for (e = 0; e < len; e++) {
220                 obj = board.objectsList[e];
221                 element = {};
222 
223                 if (!obj.dumped && obj.dump) {
224                     element.type = obj.getType();
225                     element.parents = obj.getParents().slice();
226 
227                     // Extract coordinates of a point
228                     if (element.type === 'point' && element.parents[0] === 1) {
229                         element.parents = element.parents.slice(1);
230                     }
231 
232                     for (s = 0; s < element.parents.length; s++) {
233                         if (Type.isString(element.parents[s]) &&
234                                 element.parents[s][0] !== "'" &&
235                                 element.parents[s][0] !== '"') {
236 
237                             element.parents[s] = '"' + element.parents[s] + '"';
238                         } else if (Type.isArray( element.parents[s]) ) {
239                             element.parents[s] = '[' + element.parents[s].toString() + ']';
240                         }
241                     }
242 
243                     element.attributes = this.prepareAttributes(board, obj);
244                     if (element.type === 'glider' && obj.onPolygon) {
245                         props.push({
246                             obj: obj.id,
247                             prop: 'onPolygon',
248                             val: true
249                         });
250                     }
251 
252                     elementList.push(element);
253                 }
254             }
255 
256             this.deleteMarkers(board, 'dumped');
257 
258             return {
259                 elements: elementList,
260                 props: props,
261                 methods: methods
262             };
263         },
264 
265         /**
266          * Converts an array of different values into a parameter string that can be used by the code generators.
267          * @param {Array} a
268          * @param {function} converter A function that is used to transform the elements of <tt>a</tt>. Usually
269          * {@link JXG.toJSON} or {@link JXG.Dump.toJCAN} are used.
270          * @returns {String}
271          */
272         arrayToParamStr: function (a, converter) {
273             var i,
274                 s = [];
275 
276             for (i = 0; i < a.length; i++) {
277                 s.push(converter.call(this, a[i]));
278             }
279 
280             return s.join(', ');
281         },
282 
283         /**
284          * Converts a JavaScript object into a JCAN (JessieCode Attribute Notation) string.
285          * @param {Object} obj A JavaScript object, functions will be ignored.
286          * @returns {String} The given object stored in a JCAN string.
287          */
288         toJCAN: function (obj) {
289             var s, i, list, prop;
290 
291             switch (typeof obj) {
292             case 'object':
293                 if (obj) {
294                     list = [];
295 
296                     if (Type.isArray(obj)) {
297                         for (i = 0; i < obj.length; i++) {
298                             list.push(this.toJCAN(obj[i]));
299                         }
300 
301                         return '[' + list.join(',') + ']';
302                     }
303 
304                     for (prop in obj) {
305                         if (obj.hasOwnProperty(prop)) {
306                             list.push(prop + ': ' + this.toJCAN(obj[prop]));
307                         }
308                     }
309 
310                     return '<<' + list.join(', ') + '>> ';
311                 }
312                 return 'null';
313             case 'string':
314                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
315             case 'number':
316             case 'boolean':
317                 return obj.toString();
318             case 'null':
319                 return 'null';
320             }
321         },
322 
323         /**
324          * Saves the construction in <tt>board</tt> to JessieCode.
325          * @param {JXG.Board} board
326          * @returns {String} JessieCode
327          */
328         toJessie: function (board) {
329             var i, elements,
330                 dump = this.dump(board),
331                 script = [];
332 
333             dump.methods = this.setBoundingBox(dump.methods, board, '$board');
334 
335             elements = dump.elements;
336 
337             for (i = 0; i < elements.length; i++) {
338                 if (elements[i].attributes.name.length > 0) {
339                     script.push('// ' + elements[i].attributes.name);
340                 }
341 
342                 script.push('s' + i + ' = ' + elements[i].type + '(' + elements[i].parents.join(', ') + ') ' + this.toJCAN(elements[i].attributes).replace(/\n/, '\\n') + ';');
343                 script.push('');
344             }
345 
346             for (i = 0; i < dump.methods.length; i++) {
347                 script.push(dump.methods[i].obj + '.' + dump.methods[i].method + '(' + this.arrayToParamStr(dump.methods[i].params, this.toJCAN) + ');');
348                 script.push('');
349             }
350 
351             for (i = 0; i < dump.props.length; i++) {
352                 script.push(dump.props[i].obj + '.' + dump.props[i].prop + ' = ' + this.toJCAN(dump.props[i].val) + ';');
353                 script.push('');
354             }
355 
356             return script.join('\n');
357         },
358 
359         /**
360          * Saves the construction in <tt>board</tt> to JavaScript.
361          * @param {JXG.Board} board
362          * @returns {String} JavaScript
363          */
364         toJavaScript: function (board) {
365             var i, elements,
366                 dump = this.dump(board),
367                 script = [];
368 
369             dump.methods = this.setBoundingBox(dump.methods, board, 'board');
370 
371             elements = dump.elements;
372 
373             for (i = 0; i < elements.length; i++) {
374                 script.push('board.create("' + elements[i].type + '", [' + elements[i].parents.join(', ') + '], ' + Type.toJSON(elements[i].attributes) + ');');
375             }
376 
377             for (i = 0; i < dump.methods.length; i++) {
378                 script.push(dump.methods[i].obj + '.' + dump.methods[i].method + '(' + this.arrayToParamStr(dump.methods[i].params, Type.toJSON) + ');');
379                 script.push('');
380             }
381 
382             for (i = 0; i < dump.props.length; i++) {
383                 script.push(dump.props[i].obj + '.' + dump.props[i].prop + ' = ' + Type.toJSON(dump.props[i].val) + ';');
384                 script.push('');
385             }
386 
387             return script.join('\n');
388         }
389     };
390 
391     return JXG.Dump;
392 });
393