      1 /*
      2     Copyright 2008-2013
      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             for (i = defaults.length; i > 0; i--) {
    142                 def = Type.deepCopy(def, defaults[i - 1], true);
    143             }
    144
    145             for (p in def) {
    146                 if (def.hasOwnProperty(p)) {
    147                     pl = p.toLowerCase();
    148
    149                     if (typeof def[p] !== 'object' && def[p] === copy[pl]) {
    150                         delete copy[pl];
    151                     }
    152                 }
    153             }
    154
    155             return copy;
    156         },
    157
    158         /**
    159          * Prepare the attributes object for an element to be dumped as JavaScript or JessieCode code.
    160          * @param {JXG.Board} board
    161          * @param {JXG.GeometryElement} obj Geometry element which attributes object is generated
    162          * @returns {Object} An attributes object.
    163          */
    164         prepareAttributes: function (board, obj) {
    165             var a, s;
    166
    167             a = this.minimizeObject(obj.getAttributes(), JXG.Options[obj.elType]);
    168
    169             for (s in obj.subs) {
    170                 if (obj.subs.hasOwnProperty(s)) {
    171                     a[s] = this.minimizeObject(obj.subs[s].getAttributes(),
    172                                                 JXG.Options[obj.elType][s],
    173                                                 JXG.Options[obj.subs[s].elType]);
    174                     a[s].id = obj.subs[s].id;
    175                     a[s].name = obj.subs[s].name;
    176                 }
    177             }
    178
    179             a.id = obj.id;
    180             a.name = obj.name;
    181
    182             return a;
    183         },
    184
    185         setBoundingBox: function(methods, board, boardVarName) {
    186             methods.push({
    187                 obj: boardVarName,
    188                 method: 'setBoundingBox',
    189                 params: [board.getBoundingBox(), true]
    190             });
    191
    192             return methods;
    193         },
    194
    195         /**
    196          * Generate a save-able structure with all elements. This is used by {@link JXG.Dump#toJessie} and
    197          * {@link JXG.Dump#toJavaScript} to generate the script.
    198          * @param {JXG.Board} board
    199          * @returns {Array} An array with all metadata necessary to save the construction.
    200          */
    201         dump: function (board) {
    202             var e, obj, element, s,
    203                 props = [],
    204                 methods = [],
    205                 elementList = [],
    206                 len = board.objectsList.length;
    207
    208             this.addMarkers(board, 'dumped', false);
    209
    210             // This has been moved to toJavaScript and toJessie
    211             /*
    212             methods.push({
    213                 obj: '$board',
    214                 method: 'setBoundingBox',
    215                 params: [board.getBoundingBox(), true]
    216             });
    217             */
    218
    219             for (e = 0; e < len; e++) {
    220                 obj = board.objectsList[e];
    221                 element = {};
    222
    223                 if (!obj.dumped && obj.dump) {
    224                     element.type = obj.getType();
    225                     element.parents = obj.getParents().slice();
    226
    227                     // Extract coordinates of a point
    228                     if (element.type === 'point' && element.parents[0] === 1) {
    229                         element.parents = element.parents.slice(1);
    230                     }
    231
    232                     for (s = 0; s < element.parents.length; s++) {
    233                         if (Type.isString(element.parents[s]) &&
    234                                 element.parents[s][0] !== "'" &&
    235                                 element.parents[s][0] !== '"') {
    236
    237                             element.parents[s] = '"' + element.parents[s] + '"';
    238                         } else if (Type.isArray( element.parents[s]) ) {
    239                             element.parents[s] = '[' + element.parents[s].toString() + ']';
    240                         }
    241                     }
    242
    243                     element.attributes = this.prepareAttributes(board, obj);
    244                     if (element.type === 'glider' && obj.onPolygon) {
    245                         props.push({
    246                             obj: obj.id,
    247                             prop: 'onPolygon',
    248                             val: true
    249                         });
    250                     }
    251
    252                     elementList.push(element);
    253                 }
    254             }
    255
    256             this.deleteMarkers(board, 'dumped');
    257
    258             return {
    259                 elements: elementList,
    260                 props: props,
    261                 methods: methods
    262             };
    263         },
    264
    265         /**
    266          * Converts an array of different values into a parameter string that can be used by the code generators.
    267          * @param {Array} a
    268          * @param {function} converter A function that is used to transform the elements of <tt>a</tt>. Usually
    269          * {@link JXG.toJSON} or {@link JXG.Dump.toJCAN} are used.
    270          * @returns {String}
    271          */
    272         arrayToParamStr: function (a, converter) {
    273             var i,
    274                 s = [];
    275
    276             for (i = 0; i < a.length; i++) {
    277                 s.push(converter.call(this, a[i]));
    278             }
    279
    280             return s.join(', ');
    281         },
    282
    283         /**
    284          * Converts a JavaScript object into a JCAN (JessieCode Attribute Notation) string.
    285          * @param {Object} obj A JavaScript object, functions will be ignored.
    286          * @returns {String} The given object stored in a JCAN string.
    287          */
    288         toJCAN: function (obj) {
    289             var s, i, list, prop;
    290
    291             switch (typeof obj) {
    292             case 'object':
    293                 if (obj) {
    294                     list = [];
    295
    296                     if (Type.isArray(obj)) {
    297                         for (i = 0; i < obj.length; i++) {
    298                             list.push(this.toJCAN(obj[i]));
    299                         }
    300
    301                         return '[' + list.join(',') + ']';
    302                     }
    303
    304                     for (prop in obj) {
    305                         if (obj.hasOwnProperty(prop)) {
    306                             list.push(prop + ': ' + this.toJCAN(obj[prop]));
    307                         }
    308                     }
    309
    310                     return '<<' + list.join(', ') + '>> ';
    311                 }
    312                 return 'null';
    313             case 'string':
    314                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
    315             case 'number':
    316             case 'boolean':
    317                 return obj.toString();
    318             case 'null':
    319                 return 'null';
    320             }
    321         },
    322
    323         /**
    324          * Saves the construction in <tt>board</tt> to JessieCode.
    325          * @param {JXG.Board} board
    326          * @returns {String} JessieCode
    327          */
    328         toJessie: function (board) {
    329             var i, elements,
    330                 dump = this.dump(board),
    331                 script = [];
    332
    333             dump.methods = this.setBoundingBox(dump.methods, board, '$board');
    334
    335             elements = dump.elements;
    336
    337             for (i = 0; i < elements.length; i++) {
    338                 if (elements[i].attributes.name.length > 0) {
    339                     script.push('// ' + elements[i].attributes.name);
    340                 }
    341
    342                 script.push('s' + i + ' = ' + elements[i].type + '(' + elements[i].parents.join(', ') + ') ' + this.toJCAN(elements[i].attributes).replace(/\n/, '\\n') + ';');
    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,
    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
    377             for (i = 0; i < dump.methods.length; i++) {
    378                 script.push(dump.methods[i].obj + '.' + dump.methods[i].method + '(' + this.arrayToParamStr(dump.methods[i].params, Type.toJSON) + ');');
    379                 script.push('');
    380             }
    381
    382             for (i = 0; i < dump.props.length; i++) {
    383                 script.push(dump.props[i].obj + '.' + dump.props[i].prop + ' = ' + Type.toJSON(dump.props[i].val) + ';');
    384                 script.push('');
    385             }
    386
    387             return script.join('\n');
    388         }
    389     };
    390
    391     return JXG.Dump;
    392 });
    393