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