      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