1 /* 2 Copyright 2008-2024 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 <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview The JXG.Dump namespace provides methods to save a board to javascript. 37 */ 38 39 import JXG from "../jxg.js"; 40 import Type from "./type.js"; 41 42 /** 43 * The JXG.Dump namespace provides classes and methods to save a board to javascript. 44 * @namespace 45 */ 46 JXG.Dump = { 47 /** 48 * Adds markers to every element of the board 49 * @param {JXG.Board} board 50 * @param {Array|String} markers 51 * @param {Array} values 52 */ 53 addMarkers: function (board, markers, values) { 54 var e, l, i; 55 56 if (!Type.isArray(markers)) { 57 markers = [markers]; 58 } 59 60 if (!Type.isArray(values)) { 61 values = [values]; 62 } 63 64 l = Math.min(markers.length, values.length); 65 66 markers.length = l; 67 values.length = l; 68 69 for (e in board.objects) { 70 if (board.objects.hasOwnProperty(e)) { 71 for (i = 0; i < l; i++) { 72 board.objects[e][markers[i]] = values[i]; 73 } 74 } 75 } 76 }, 77 78 /** 79 * Removes markers from every element on the board. 80 * @param {JXG.Board} board 81 * @param {Array|String} markers 82 */ 83 deleteMarkers: function (board, markers) { 84 var e, l, i; 85 86 if (!Type.isArray(markers)) { 87 markers = [markers]; 88 } 89 90 l = markers.length; 91 92 markers.length = l; 93 94 for (e in board.objects) { 95 if (board.objects.hasOwnProperty(e)) { 96 for (i = 0; i < l; i++) { 97 delete board.objects[e][markers[i]]; 98 } 99 } 100 } 101 }, 102 103 /** 104 * Stringifies a string, i.e. puts some quotation marks around <tt>s</tt> if it is of type string. 105 * @param {*} s 106 * @returns {String} " + s + " 107 */ 108 str: function (s) { 109 if (typeof s === "string" && s.slice(0, 7) !== "function") { 110 s = '"' + s + '"'; 111 } 112 113 return s; 114 }, 115 116 /** 117 * Eliminate default values given by {@link JXG.Options} from the attributes object. 118 * @param {Object} instance Attribute object of the element 119 * @param {Object} s Arbitrary number of objects <tt>instance</tt> will be compared to. Usually these are 120 * sub-objects of the {@link JXG.Board#options} structure. 121 * @returns {Object} Minimal attributes object 122 */ 123 minimizeObject: function (instance, s) { 124 var p, 125 pl, 126 i, 127 def = {}, 128 copy = Type.deepCopy(instance), 129 defaults = []; 130 131 for (i = 1; i < arguments.length; i++) { 132 defaults.push(arguments[i]); 133 } 134 135 def = Type.deepCopy(def, JXG.Options.elements, true); 136 for (i = defaults.length; i > 0; i--) { 137 def = Type.deepCopy(def, defaults[i - 1], true); 138 } 139 140 for (p in def) { 141 if (def.hasOwnProperty(p)) { 142 pl = p.toLowerCase(); 143 144 if (typeof def[p] !== "object" && def[p] === copy[pl]) { 145 // console.log("delete", p); 146 delete copy[pl]; 147 } 148 } 149 } 150 151 return copy; 152 }, 153 154 /** 155 * Prepare the attributes object for an element to be dumped as JavaScript or JessieCode code. 156 * @param {JXG.Board} board 157 * @param {JXG.GeometryElement} obj Geometry element which attributes object is generated 158 * @returns {Object} An attributes object. 159 */ 160 prepareAttributes: function (board, obj) { 161 var a, s; 162 163 a = this.minimizeObject(obj.getAttributes(), JXG.Options[obj.elType]); 164 165 for (s in obj.subs) { 166 if (obj.subs.hasOwnProperty(s)) { 167 a[s] = this.minimizeObject( 168 obj.subs[s].getAttributes(), 169 JXG.Options[obj.elType][s], 170 JXG.Options[obj.subs[s].elType] 171 ); 172 a[s].id = obj.subs[s].id; 173 a[s].name = obj.subs[s].name; 174 } 175 } 176 177 a.id = obj.id; 178 a.name = obj.name; 179 180 return a; 181 }, 182 183 setBoundingBox: function (methods, board, boardVarName) { 184 methods.push({ 185 obj: boardVarName, 186 method: "setBoundingBox", 187 params: [board.getBoundingBox(), board.keepaspectratio] 188 }); 189 190 return methods; 191 }, 192 193 /** 194 * Generate a save-able structure with all elements. This is used by {@link JXG.Dump#toJessie} and 195 * {@link JXG.Dump#toJavaScript} to generate the script. 196 * @param {JXG.Board} board 197 * @returns {Array} An array with all metadata necessary to save the construction. 198 */ 199 dump: function (board) { 200 var e, 201 obj, 202 element, 203 s, 204 props = [], 205 methods = [], 206 elementList = [], 207 len = board.objectsList.length; 208 209 this.addMarkers(board, "dumped", false); 210 211 for (e = 0; e < len; e++) { 212 obj = board.objectsList[e]; 213 element = {}; 214 215 if (!obj.dumped && obj.dump) { 216 element.type = obj.getType(); 217 element.parents = obj.getParents().slice(); 218 219 // Extract coordinates of a point 220 if (element.type === "point" && element.parents[0] === 1) { 221 element.parents = element.parents.slice(1); 222 } 223 224 for (s = 0; s < element.parents.length; s++) { 225 if ( 226 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, 323 elements, 324 id, 325 dump = this.dump(board), 326 script = []; 327 328 dump.methods = this.setBoundingBox(dump.methods, board, "$board"); 329 330 elements = dump.elements; 331 332 for (i = 0; i < elements.length; i++) { 333 if (elements[i].attributes.name.length > 0) { 334 script.push("// " + elements[i].attributes.name); 335 } 336 script.push( 337 "s" + i + " = " + elements[i].type + "(" + elements[i].parents.join(", ") + ") " + this.toJCAN(elements[i].attributes).replace(/\n/, "\\n") + ";" 338 ); 339 340 if (elements[i].type === "axis") { 341 // Handle the case that remove[All]Ticks had been called. 342 id = elements[i].attributes.id; 343 if (board.objects[id].defaultTicks === null) { 344 script.push("s" + i + ".removeAllTicks();"); 345 } 346 } 347 script.push(""); 348 } 349 350 for (i = 0; i < dump.methods.length; i++) { 351 script.push( 352 dump.methods[i].obj + 353 "." + 354 dump.methods[i].method + 355 "(" + 356 this.arrayToParamStr(dump.methods[i].params, this.toJCAN) + 357 ");" 358 ); 359 script.push(""); 360 } 361 362 for (i = 0; i < dump.props.length; i++) { 363 script.push( 364 dump.props[i].obj + 365 "." + 366 dump.props[i].prop + 367 " = " + 368 this.toJCAN(dump.props[i].val) + 369 ";" 370 ); 371 script.push(""); 372 } 373 374 return script.join("\n"); 375 }, 376 377 /** 378 * Saves the construction in <tt>board</tt> to JavaScript. 379 * @param {JXG.Board} board 380 * @returns {String} JavaScript 381 */ 382 toJavaScript: function (board) { 383 var i, 384 elements, 385 id, 386 dump = this.dump(board), 387 script = []; 388 389 dump.methods = this.setBoundingBox(dump.methods, board, "board"); 390 391 elements = dump.elements; 392 393 for (i = 0; i < elements.length; i++) { 394 script.push( 395 'board.create("' + 396 elements[i].type + 397 '", [' + 398 elements[i].parents.join(", ") + 399 "], " + 400 Type.toJSON(elements[i].attributes) + 401 ");" 402 ); 403 404 if (elements[i].type === "axis") { 405 // Handle the case that remove[All]Ticks had been called. 406 id = elements[i].attributes.id; 407 if (board.objects[id].defaultTicks === null) { 408 script.push( 409 'board.objects["' + 410 id + 411 '"].removeTicks(board.objects["' + 412 id + 413 '"].defaultTicks);' 414 ); 415 } 416 } 417 } 418 419 for (i = 0; i < dump.methods.length; i++) { 420 script.push( 421 dump.methods[i].obj + 422 "." + 423 dump.methods[i].method + 424 "(" + 425 this.arrayToParamStr(dump.methods[i].params, Type.toJSON) + 426 ");" 427 ); 428 script.push(""); 429 } 430 431 for (i = 0; i < dump.props.length; i++) { 432 script.push( 433 dump.props[i].obj + 434 "." + 435 dump.props[i].prop + 436 " = " + 437 Type.toJSON(dump.props[i].val) + 438 ";" 439 ); 440 script.push(""); 441 } 442 443 return script.join("\n"); 444 } 445 }; 446 447 export default JXG.Dump; 448