1 /*
  2     Copyright 2008-2011
  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: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 */
 25 
 26 /**
 27  * @fileoverview The JXG.Dump namespace provides methods to save a board to javascript.
 28  */
 29 
 30 /**
 31  * The JXG.Dump namespace provides classes and methods to save a board to javascript.
 32  * @namespace
 33  */
 34 JXG.Dump = {
 35 
 36     /**
 37      * Adds markers to every element of the board
 38      * @param {JXG.Board} board
 39      * @param {Array|String} markers
 40      * @param {Array|%} values
 41      */
 42     addMarkers: function (board, markers, values) {
 43         var e, l, i;
 44 
 45         if (!JXG.isArray(markers)) {
 46             markers = [markers];
 47         }
 48 
 49         if (!JXG.isArray(values)) {
 50             values = [values];
 51         }
 52 
 53         l = Math.min(markers.length, values.length);
 54 
 55         markers.length = l;
 56         values.length = l;
 57 
 58         for (e in board.objects) {
 59             for (i = 0; i < l; i++) {
 60                 board.objects[e][markers[i]] = values[i];
 61             }
 62         }
 63     },
 64 
 65     /**
 66      * Removes markers from every element on the board.
 67      * @param {JXG.Board} board
 68      * @param {Array|String} markers
 69      */
 70     deleteMarkers: function (board, markers) {
 71         var e, l, i;
 72 
 73         if (!JXG.isArray(markers)) {
 74             markers = [markers];
 75         }
 76 
 77         l = markers.length;
 78 
 79         markers.length = l;
 80 
 81         for (e in board.objects) {
 82             for (i = 0; i < l; i++) {
 83                 delete board.objects[e][markers[i]];
 84             }
 85         }
 86     },
 87 
 88     /**
 89      * Stringifies a string, i.e. puts some quotation marks around <tt>s</tt> if it is of type string.
 90      * @param {%} s
 91      * @returns {String} " + s + "
 92      */
 93     str: function (s) {
 94         if (typeof s === 'string' && s.substr(0, 7) !== 'function') {
 95             s = '\'' + s + '\'';
 96         }
 97 
 98         return s;
 99     },
100 
101     /**
102      * Eliminate default values given by {@link JXG.Options} from the attributes object.
103      * @param {Object} instance Attribute object of the element
104      * @param {Object} % Arbitrary number of objects <tt>instance</tt> will be compared to. Usually these are
105      * sub-objects of the {@link JXG.Board#options} structure.
106      * @returns {Object} Minimal attributes object
107      */
108     minimizeObject: function (instance) {
109         var p, pl, copy = JXG.deepCopy(instance),
110             defaults = [], i;
111 
112         for (i = 1; i < arguments.length; i++) {
113             defaults.push(arguments[i]);
114         }
115 
116         for (i = 0; i < defaults.length; i++) {
117             for (p in defaults[i]) {
118                 pl = p.toLowerCase();
119 
120                 if (typeof defaults[i][p] !== 'object' && defaults[i][p] == copy[pl]) {
121                     delete copy[pl];
122                 }
123             }
124         }
125 
126         return copy;
127     },
128 
129     /**
130      * Prepare the attributes object for an element.
131      * @param {JXG.Board} board
132      * @param {JXG.GeometryElement} obj Geometry element which attributes object is generated
133      * @returns {Object} An attributes object.
134      */
135     prepareAttributes: function (board, obj) {
136         var a, s;
137 
138         a = this.minimizeObject(obj.getAttributes(), board.options[obj.elType], board.options.elements);
139 
140         for (s in obj.subs) {
141             a[s] = this.minimizeObject(obj.subs[s].getAttributes(), board.options[obj.elType][s], board.options[obj.subs[s].elType], board.options.elements);
142             a[s].id = obj.subs[s].id;
143             a[s].name = obj.subs[s].name;
144         }
145 
146         a.id = obj.id;
147         a.name = obj.name;
148 
149         return a;
150     },
151 
152     /**
153      * Generate a save-able structure with all elements. This is used by {@link JXG.Dump#toJessie} and {@link JXG.Dump#toJavaScript}
154      * to generate the script.
155      * @param {JXG.Board} board
156      * @returns {Array} An array with all metadata necessary to save the construction.
157      */
158     dump: function (board) {
159         var e, obj, element, s,
160             elementList = [], i;
161 
162         this.addMarkers(board, 'dumped', false);
163 
164         for (e in board.objects) {
165             obj = board.objects[e];
166             element = {};
167 
168             if (!obj.dumped && obj.dump) {
169                 element.type = obj.getType();
170                 element.parents = obj.getParents();
171 
172                 if (element.type === 'point' && element.parents[0] == 1) {
173                     element.parents = element.parents.slice(1);
174                 }
175 
176                 for (s = 0; s < element.parents.length; s++) {
177                     if (typeof element.parents[s] === 'string') {
178                         element.parents[s] = '\'' + element.parents[s] + '\'';
179                     }
180                 }
181 
182                 element.attributes = this.prepareAttributes(board, obj);
183 
184                 elementList.push(element);
185             }
186         }
187 
188         this.deleteMarkers(board, 'dumped');
189 
190         return elementList;
191     },
192 
193     /**
194      * Converts a JavaScript object into a JSAN (JessieScript Attribute Notation) string.
195      * @param {Object} obj A JavaScript object, functions will be ignored.
196      * @returns {String} The given object stored in a JSAN string.
197      */
198     toJSAN: function (obj) {
199         var s, i;
200 
201         switch (typeof obj) {
202             case 'object':
203                 if (obj) {
204                     var list = [];
205                     if (obj instanceof Array) {
206                         for (i = 0; i < obj.length; i++) {
207                             list.push(this.toJSAN(obj[i]));
208                         }
209                         return '[' + list.join(',') + ']';
210                     } else {
211                         for (var prop in obj) {
212                             list.push(prop + ': ' + this.toJSAN(obj[prop]));
213                         }
214                         return '<<' + list.join(', ') + '>> ';
215                     }
216                 } else {
217                     return 'null';
218                 }
219             case 'string':
220                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
221             case 'number':
222             case 'boolean':
223                 return new String(obj);
224             case 'null':
225                 return 'null';
226         }
227     },
228 
229     /**
230      * Saves the construction in <tt>board</tt> to JessieScript.
231      * @param {JXG.Board} board
232      * @returns {String} JessieScript
233      */
234     toJessie: function (board) {
235         var elements = this.dump(board),
236             script = [], i;
237 
238         for (i = 0; i < elements.length; i++) {
239             if (elements[i].attributes.name.length > 0) {
240                 script.push('// ' + elements[i].attributes.name);
241             }
242 
243             script.push('s' + i + ' = ' + elements[i].type + '(' + elements[i].parents.join(', ') + ') ' + this.toJSAN(elements[i].attributes).replace(/\n/, '\\n') + ';');
244             script.push('');
245         }
246 
247         return script.join('\n');
248     },
249 
250     /**
251      * Saves the construction in <tt>board</tt> to JavaScript.
252      * @param {JXG.Board} board
253      * @returns {String} JavaScript
254      */
255     toJavaScript: function (board) {
256         var elements = this.dump(board),
257             script = [], i;
258 
259         for (i = 0; i < elements.length; i++) {
260             script.push('board.create("' + elements[i].type + '", [' + elements[i].parents.join(', ') + '], ' + JXG.toJSON(elements[i].attributes) + ');');
261         }
262 
263         return script.join('\n');
264     }
265 };