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