/** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxObjectCodec * * Generic codec for JavaScript objects that implements a mapping between * JavaScript objects and XML nodes that maps each field or element to an * attribute or child node, and vice versa. * * Atomic Values: * * Consider the following example. * * (code) * var obj = new Object(); * obj.foo = "Foo"; * obj.bar = "Bar"; * (end) * * This object is encoded into an XML node using the following. * * (code) * var enc = new mxCodec(); * var node = enc.encode(obj); * (end) * * The output of the encoding may be viewed using as follows. * * (code) * mxLog.show(); * mxLog.debug(mxUtils.getPrettyXml(node)); * (end) * * Finally, the result of the encoding looks as follows. * * (code) * * (end) * * In the above output, the foo and bar fields have been mapped to attributes * with the same names, and the name of the constructor was used for the * nodename. * * Booleans: * * Since booleans are numbers in JavaScript, all boolean values are encoded * into 1 for true and 0 for false. The decoder also accepts the string true * and false for boolean values. * * Objects: * * The above scheme is applied to all atomic fields, that is, to all non-object * fields of an object. For object fields, a child node is created with a * special attribute that contains the fieldname. This special attribute is * called "as" and hence, as is a reserved word that should not be used for a * fieldname. * * Consider the following example where foo is an object and bar is an atomic * property of foo. * * (code) * var obj = {foo: {bar: "Bar"}}; * (end) * * This will be mapped to the following XML structure by mxObjectCodec. * * (code) * * * * (end) * * In the above output, the inner Object node contains the as-attribute that * specifies the fieldname in the enclosing object. That is, the field foo was * mapped to a child node with an as-attribute that has the value foo. * * Arrays: * * Arrays are special objects that are either associative, in which case each * key, value pair is treated like a field where the key is the fieldname, or * they are a sequence of atomic values and objects, which is mapped to a * sequence of child nodes. For object elements, the above scheme is applied * without the use of the special as-attribute for creating each child. For * atomic elements, a special add-node is created with the value stored in the * value-attribute. * * For example, the following array contains one atomic value and one object * with a field called bar. Furthermore it contains two associative entries * called bar with an atomic value, and foo with an object value. * * (code) * var obj = ["Bar", {bar: "Bar"}]; * obj["bar"] = "Bar"; * obj["foo"] = {bar: "Bar"}; * (end) * * This array is represented by the following XML nodes. * * (code) * * * * * * (end) * * The Array node name is the name of the constructor. The additional * as-attribute in the last child contains the key of the associative entry, * whereas the second last child is part of the array sequence and does not * have an as-attribute. * * References: * * Objects may be represented as child nodes or attributes with ID values, * which are used to lookup the object in a table within . The * function is in charge of deciding if a specific field should * be encoded as a reference or not. Its default implementation returns true if * the fieldname is in , an array of strings that is used to configure * the . * * Using this approach, the mapping does not guarantee that the referenced * object itself exists in the document. The fields that are encoded as * references must be carefully chosen to make sure all referenced objects * exist in the document, or may be resolved by some other means if necessary. * * For example, in the case of the graph model all cells are stored in a tree * whose root is referenced by the model's root field. A tree is a structure * that is well suited for an XML representation, however, the additional edges * in the graph model have a reference to a source and target cell, which are * also contained in the tree. To handle this case, the source and target cell * of an edge are treated as references, whereas the children are treated as * objects. Since all cells are contained in the tree and no edge references a * source or target outside the tree, this setup makes sure all referenced * objects are contained in the document. * * In the case of a tree structure we must further avoid infinite recursion by * ignoring the parent reference of each child. This is done by returning true * in , whose default implementation uses the array of excluded * fieldnames passed to the mxObjectCodec constructor. * * References are only used for cells in mxGraph. For defining other * referencable object types, the codec must be able to work out the ID of an * object. This is done by implementing . For decoding a * reference, the XML node with the respective id-attribute is fetched from the * document, decoded, and stored in a lookup table for later reference. For * looking up external objects, may be implemented. * * Expressions: * * For decoding JavaScript expressions, the add-node may be used with a text * content that contains the JavaScript expression. For example, the following * creates a field called foo in the enclosing object and assigns it the value * of . * * (code) * * mxConstants.ALIGN_LEFT * * (end) * * The resulting object has a field called foo with the value "left". Its XML * representation looks as follows. * * (code) * * (end) * * This means the expression is evaluated at decoding time and the result of * the evaluation is stored in the respective field. Valid expressions are all * JavaScript expressions, including function definitions, which are mapped to * functions on the resulting object. * * Expressions are only evaluated if is true. * * Constructor: mxObjectCodec * * Constructs a new codec for the specified template object. * The variables in the optional exclude array are ignored by * the codec. Variables in the optional idrefs array are * turned into references in the XML. The optional mapping * may be used to map from variable names to XML attributes. * The argument is created as follows: * * (code) * var mapping = new Object(); * mapping['variableName'] = 'attribute-name'; * (end) * * Parameters: * * template - Prototypical instance of the object to be * encoded/decoded. * exclude - Optional array of fieldnames to be ignored. * idrefs - Optional array of fieldnames to be converted to/from * references. * mapping - Optional mapping from field- to attributenames. */ function mxObjectCodec(template, exclude, idrefs, mapping) { this.template = template; this.exclude = (exclude != null) ? exclude : []; this.idrefs = (idrefs != null) ? idrefs : []; this.mapping = (mapping != null) ? mapping : []; this.reverse = new Object(); for (var i in this.mapping) { this.reverse[this.mapping[i]] = i; } }; /** * Variable: allowEval * * Static global switch that specifies if expressions in arrays are allowed. * Default is false. NOTE: Enabling this carries a possible security risk. */ mxObjectCodec.allowEval = false; /** * Variable: template * * Holds the template object associated with this codec. */ mxObjectCodec.prototype.template = null; /** * Variable: exclude * * Array containing the variable names that should be * ignored by the codec. */ mxObjectCodec.prototype.exclude = null; /** * Variable: idrefs * * Array containing the variable names that should be * turned into or converted from references. See * and . */ mxObjectCodec.prototype.idrefs = null; /** * Variable: mapping * * Maps from from fieldnames to XML attribute names. */ mxObjectCodec.prototype.mapping = null; /** * Variable: reverse * * Maps from from XML attribute names to fieldnames. */ mxObjectCodec.prototype.reverse = null; /** * Function: getName * * Returns the name used for the nodenames and lookup of the codec when * classes are encoded and nodes are decoded. For classes to work with * this the codec registry automatically adds an alias for the classname * if that is different than what this returns. The default implementation * returns the classname of the template class. */ mxObjectCodec.prototype.getName = function() { return mxUtils.getFunctionName(this.template.constructor); }; /** * Function: cloneTemplate * * Returns a new instance of the template for this codec. */ mxObjectCodec.prototype.cloneTemplate = function() { return new this.template.constructor(); }; /** * Function: getFieldName * * Returns the fieldname for the given attributename. * Looks up the value in the mapping or returns * the input if there is no reverse mapping for the * given name. */ mxObjectCodec.prototype.getFieldName = function(attributename) { if (attributename != null) { var mapped = this.reverse[attributename]; if (mapped != null) { attributename = mapped; } } return attributename; }; /** * Function: getAttributeName * * Returns the attributename for the given fieldname. * Looks up the value in the or returns * the input if there is no mapping for the * given name. */ mxObjectCodec.prototype.getAttributeName = function(fieldname) { if (fieldname != null) { var mapped = this.mapping[fieldname]; if (mapped != null) { fieldname = mapped; } } return fieldname; }; /** * Function: isExcluded * * Returns true if the given attribute is to be ignored by the codec. This * implementation returns true if the given fieldname is in or * if the fieldname equals . * * Parameters: * * obj - Object instance that contains the field. * attr - Fieldname of the field. * value - Value of the field. * write - Boolean indicating if the field is being encoded or decoded. * Write is true if the field is being encoded, else it is being decoded. */ mxObjectCodec.prototype.isExcluded = function(obj, attr, value, write) { return attr == mxObjectIdentity.FIELD_NAME || mxUtils.indexOf(this.exclude, attr) >= 0; }; /** * Function: isReference * * Returns true if the given fieldname is to be treated * as a textual reference (ID). This implementation returns * true if the given fieldname is in . * * Parameters: * * obj - Object instance that contains the field. * attr - Fieldname of the field. * value - Value of the field. * write - Boolean indicating if the field is being encoded or decoded. * Write is true if the field is being encoded, else it is being decoded. */ mxObjectCodec.prototype.isReference = function(obj, attr, value, write) { return mxUtils.indexOf(this.idrefs, attr) >= 0; }; /** * Function: encode * * Encodes the specified object and returns a node * representing then given object. Calls * after creating the node and with the * resulting node after processing. * * Enc is a reference to the calling encoder. It is used * to encode complex objects and create references. * * This implementation encodes all variables of an * object according to the following rules: * * - If the variable name is in then it is ignored. * - If the variable name is in then * is used to replace the object with its ID. * - The variable name is mapped using . * - If obj is an array and the variable name is numeric * (ie. an index) then it is not encoded. * - If the value is an object, then the codec is used to * create a child node with the variable name encoded into * the "as" attribute. * - Else, if is true or the value differs * from the template value, then ... * - ... if obj is not an array, then the value is mapped to * an attribute. * - ... else if obj is an array, the value is mapped to an * add child with a value attribute or a text child node, * if the value is a function. * * If no ID exists for a variable in or if an object * cannot be encoded, a warning is issued using . * * Returns the resulting XML node that represents the given * object. * * Parameters: * * enc - that controls the encoding process. * obj - Object to be encoded. */ mxObjectCodec.prototype.encode = function(enc, obj) { var node = enc.document.createElement(this.getName()); obj = this.beforeEncode(enc, obj, node); this.encodeObject(enc, obj, node); return this.afterEncode(enc, obj, node); }; /** * Function: encodeObject * * Encodes the value of each member in then given obj into the given node using * . * * Parameters: * * enc - that controls the encoding process. * obj - Object to be encoded. * node - XML node that contains the encoded object. */ mxObjectCodec.prototype.encodeObject = function(enc, obj, node) { enc.setAttribute(node, 'id', enc.getId(obj)); for (var i in obj) { var name = i; var value = obj[name]; if (value != null && !this.isExcluded(obj, name, value, true)) { if (mxUtils.isInteger(name)) { name = null; } this.encodeValue(enc, obj, name, value, node); } } }; /** * Function: encodeValue * * Converts the given value according to the mappings * and id-refs in this codec and uses * to write the attribute into the given node. * * Parameters: * * enc - that controls the encoding process. * obj - Object whose property is going to be encoded. * name - XML node that contains the encoded object. * value - Value of the property to be encoded. * node - XML node that contains the encoded object. */ mxObjectCodec.prototype.encodeValue = function(enc, obj, name, value, node) { if (value != null) { if (this.isReference(obj, name, value, true)) { var tmp = enc.getId(value); if (tmp == null) { mxLog.warn('mxObjectCodec.encode: No ID for ' + this.getName() + '.' + name + '=' + value); return; // exit } value = tmp; } var defaultValue = this.template[name]; // Checks if the value is a default value and // the name is correct if (name == null || enc.encodeDefaults || defaultValue != value) { name = this.getAttributeName(name); this.writeAttribute(enc, obj, name, value, node); } } }; /** * Function: writeAttribute * * Writes the given value into node using * or depending on the type of the value. */ mxObjectCodec.prototype.writeAttribute = function(enc, obj, name, value, node) { if (typeof(value) != 'object' /* primitive type */) { this.writePrimitiveAttribute(enc, obj, name, value, node); } else /* complex type */ { this.writeComplexAttribute(enc, obj, name, value, node); } }; /** * Function: writePrimitiveAttribute * * Writes the given value as an attribute of the given node. */ mxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj, name, value, node) { value = this.convertAttributeToXml(enc, obj, name, value, node); if (name == null) { var child = enc.document.createElement('add'); if (typeof(value) == 'function') { child.appendChild(enc.document.createTextNode(value)); } else { enc.setAttribute(child, 'value', value); } node.appendChild(child); } else if (typeof(value) != 'function') { enc.setAttribute(node, name, value); } }; /** * Function: writeComplexAttribute * * Writes the given value as a child node of the given node. */ mxObjectCodec.prototype.writeComplexAttribute = function(enc, obj, name, value, node) { var child = enc.encode(value); if (child != null) { if (name != null) { child.setAttribute('as', name); } node.appendChild(child); } else { mxLog.warn('mxObjectCodec.encode: No node for ' + this.getName() + '.' + name + ': ' + value); } }; /** * Function: convertAttributeToXml * * Converts true to "1" and false to "0" is returns true. * All other values are not converted. * * Parameters: * * enc - that controls the encoding process. * obj - Objec to convert the attribute for. * name - Name of the attribute to be converted. * value - Value to be converted. */ mxObjectCodec.prototype.convertAttributeToXml = function(enc, obj, name, value) { // Makes sure to encode boolean values as numeric values if (this.isBooleanAttribute(enc, obj, name, value)) { // Checks if the value is true (do not use the value as is, because // this would check if the value is not null, so 0 would be true) value = (value == true) ? '1' : '0'; } return value; }; /** * Function: isBooleanAttribute * * Returns true if the given object attribute is a boolean value. * * Parameters: * * enc - that controls the encoding process. * obj - Objec to convert the attribute for. * name - Name of the attribute to be converted. * value - Value of the attribute to be converted. */ mxObjectCodec.prototype.isBooleanAttribute = function(enc, obj, name, value) { return (typeof(value.length) == 'undefined' && (value == true || value == false)); }; /** * Function: convertAttributeFromXml * * Converts booleans and numeric values to the respective types. Values are * numeric if returns true. * * Parameters: * * dec - that controls the decoding process. * attr - XML attribute to be converted. * obj - Objec to convert the attribute for. */ mxObjectCodec.prototype.convertAttributeFromXml = function(dec, attr, obj) { var value = attr.value; if (this.isNumericAttribute(dec, attr, obj)) { value = parseFloat(value); if (isNaN(value) || !isFinite(value)) { value = 0; } } return value; }; /** * Function: isNumericAttribute * * Returns true if the given XML attribute is or should be a numeric value. * * Parameters: * * dec - that controls the decoding process. * attr - XML attribute to be converted. * obj - Objec to convert the attribute for. */ mxObjectCodec.prototype.isNumericAttribute = function(dec, attr, obj) { // Handles known numeric attributes for generic objects var result = (obj.constructor == mxGeometry && (attr.name == 'x' || attr.name == 'y' || attr.name == 'width' || attr.name == 'height')) || (obj.constructor == mxPoint && (attr.name == 'x' || attr.name == 'y')) || mxUtils.isNumeric(attr.value); return result; }; /** * Function: beforeEncode * * Hook for subclassers to pre-process the object before * encoding. This returns the input object. The return * value of this function is used in to perform * the default encoding into the given node. * * Parameters: * * enc - that controls the encoding process. * obj - Object to be encoded. * node - XML node to encode the object into. */ mxObjectCodec.prototype.beforeEncode = function(enc, obj, node) { return obj; }; /** * Function: afterEncode * * Hook for subclassers to post-process the node * for the given object after encoding and return the * post-processed node. This implementation returns * the input node. The return value of this method * is returned to the encoder from . * * Parameters: * * enc - that controls the encoding process. * obj - Object to be encoded. * node - XML node that represents the default encoding. */ mxObjectCodec.prototype.afterEncode = function(enc, obj, node) { return node; }; /** * Function: decode * * Parses the given node into the object or returns a new object * representing the given node. * * Dec is a reference to the calling decoder. It is used to decode * complex objects and resolve references. * * If a node has an id attribute then the object cache is checked for the * object. If the object is not yet in the cache then it is constructed * using the constructor of