1 /* 2 Copyright 2008-2022 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,'\\\\').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, id, 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 script.push('s' + i + ' = ' + elements[i].type + '(' + elements[i].parents.join(', ') + ') ' + this.toJCAN(elements[i].attributes).replace(/\n/, '\\n') + ';'); 335 336 if (elements[i].type === 'axis') { 337 // Handle the case that remove[All]Ticks had been called. 338 id = elements[i].attributes.id; 339 if (board.objects[id].defaultTicks === null) { 340 script.push('s' + i + '.removeAllTicks();'); 341 } 342 } 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, id, 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 if (elements[i].type === 'axis') { 377 // Handle the case that remove[All]Ticks had been called. 378 id = elements[i].attributes.id; 379 if (board.objects[id].defaultTicks === null) { 380 script.push('board.objects["' + id + '"].removeTicks(board.objects["' + id + '"].defaultTicks);'); 381 } 382 } 383 } 384 385 for (i = 0; i < dump.methods.length; i++) { 386 script.push(dump.methods[i].obj + '.' + dump.methods[i].method + '(' + this.arrayToParamStr(dump.methods[i].params, Type.toJSON) + ');'); 387 script.push(''); 388 } 389 390 for (i = 0; i < dump.props.length; i++) { 391 script.push(dump.props[i].obj + '.' + dump.props[i].prop + ' = ' + Type.toJSON(dump.props[i].val) + ';'); 392 script.push(''); 393 } 394 395 return script.join('\n'); 396 } 397 }; 398 399 return JXG.Dump; 400 }); 401