summaryrefslogtreecommitdiff
path: root/src/js/io/mxObjectCodec.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/io/mxObjectCodec.js')
-rw-r--r--src/js/io/mxObjectCodec.js983
1 files changed, 983 insertions, 0 deletions
diff --git a/src/js/io/mxObjectCodec.js b/src/js/io/mxObjectCodec.js
new file mode 100644
index 0000000..9d2473a
--- /dev/null
+++ b/src/js/io/mxObjectCodec.js
@@ -0,0 +1,983 @@
+/**
+ * $Id: mxObjectCodec.js,v 1.49 2010-12-01 09:19:58 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * 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 <mxLog> as follows.
+ *
+ * (code)
+ * mxLog.show();
+ * mxLog.debug(mxUtils.getPrettyXml(node));
+ * (end)
+ *
+ * Finally, the result of the encoding looks as follows.
+ *
+ * (code)
+ * <Object foo="Foo" bar="Bar"/>
+ * (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)
+ * <Object>
+ * <Object bar="Bar" as="foo"/>
+ * </Object>
+ * (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)
+ * <Array bar="Bar">
+ * <add value="Bar"/>
+ * <Object bar="Bar"/>
+ * <Object bar="Bar" as="foo"/>
+ * </Array>
+ * (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 <mxCodec>. The
+ * <isReference> 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 <idrefs>, an array of strings that is used to configure
+ * the <mxObjectCodec>.
+ *
+ * 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 <isExcluded>, 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 <mxCodec.reference>. 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, <mxCodec.lookup> 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 <mxConstants.ALIGN_LEFT>.
+ *
+ * (code)
+ * <Object>
+ * <add as="foo">mxConstants.ALIGN_LEFT</add>
+ * </Object>
+ * (end)
+ *
+ * The resulting object has a field called foo with the value "left". Its XML
+ * representation looks as follows.
+ *
+ * (code)
+ * <Object foo="left"/>
+ * (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.
+ *
+ * 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: 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
+ * <mxCodec.getId> and <mxCodec.getObject>.
+ */
+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 <reverse> 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 <mapping> 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 <exclude> or
+ * if the fieldname equals <mxObjectIdentity.FIELD_NAME>.
+ *
+ * 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 <idrefs>.
+ *
+ * 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 <beforeEncode>
+ * after creating the node and <afterEncode> 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 <exclude> then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getId>
+ * is used to replace the object with its ID.
+ * - The variable name is mapped using <mapping>.
+ * - 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 <encodeDefaults> 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 <idrefs> or if an object
+ * cannot be encoded, a warning is issued using <mxLog.warn>.
+ *
+ * Returns the resulting XML node that represents the given
+ * object.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> 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
+ * <encodeValue>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> 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.isNumeric(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 <writeAttribute>
+ * to write the attribute into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> 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 <writePrimitiveAttribute>
+ * or <writeComplexAttribute> depending on the type of the value.
+ */
+mxObjectCodec.prototype.writeAttribute = function(enc, obj,
+ attr, value, node)
+{
+ if (typeof(value) != 'object' /* primitive type */)
+ {
+ this.writePrimitiveAttribute(enc, obj, attr, value, node);
+ }
+ else /* complex type */
+ {
+ this.writeComplexAttribute(enc, obj, attr, value, node);
+ }
+};
+
+/**
+ * Function: writePrimitiveAttribute
+ *
+ * Writes the given value as an attribute of the given node.
+ */
+mxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj,
+ attr, value, node)
+{
+ value = this.convertValueToXml(value);
+
+ if (attr == 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, attr, value);
+ }
+};
+
+/**
+ * Function: writeComplexAttribute
+ *
+ * Writes the given value as a child node of the given node.
+ */
+mxObjectCodec.prototype.writeComplexAttribute = function(enc, obj,
+ attr, value, node)
+{
+ var child = enc.encode(value);
+
+ if (child != null)
+ {
+ if (attr != null)
+ {
+ child.setAttribute('as', attr);
+ }
+
+ node.appendChild(child);
+ }
+ else
+ {
+ mxLog.warn('mxObjectCodec.encode: No node for ' +
+ this.getName() + '.' + attr + ': ' + value);
+ }
+};
+
+/**
+ * Function: convertValueToXml
+ *
+ * Converts true to "1" and false to "0". All other values are ignored.
+ */
+mxObjectCodec.prototype.convertValueToXml = function(value)
+{
+ // Makes sure to encode boolean values as numeric values
+ if (typeof(value.length) == 'undefined' &&
+ (value == true ||
+ value == false))
+ {
+ // 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: convertValueFromXml
+ *
+ * Converts booleans and numeric values to the respective types.
+ */
+mxObjectCodec.prototype.convertValueFromXml = function(value)
+{
+ if (mxUtils.isNumeric(value))
+ {
+ value = parseFloat(value);
+ }
+
+ return value;
+};
+
+/**
+ * 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 <encode> to perform
+ * the default encoding into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> 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 <encode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> 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 <template> and cached in <mxCodec.objects>.
+ *
+ * This implementation decodes all attributes and childs of a node
+ * according to the following rules:
+ *
+ * - If the variable name is in <exclude> or if the attribute name is "id"
+ * or "as" then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getObject> is used
+ * to replace the reference with an object.
+ * - The variable name is mapped using a reverse <mapping>.
+ * - If the value has a child node, then the codec is used to create a
+ * child object with the variable name taken from the "as" attribute.
+ * - If the object is an array and the variable name is empty then the
+ * value or child object is appended to the array.
+ * - If an add child has no value or the object is not an array then
+ * the child text content is evaluated using <mxUtils.eval>.
+ *
+ * For add nodes where the object is not an array and the variable name
+ * is defined, the default mechanism is used, allowing to override/add
+ * methods as follows:
+ *
+ * (code)
+ * <Object>
+ * <add as="hello"><![CDATA[
+ * function(arg1) {
+ * mxUtils.alert('Hello '+arg1);
+ * }
+ * ]]></add>
+ * </Object>
+ * (end)
+ *
+ * If no object exists for an ID in <idrefs> a warning is issued
+ * using <mxLog.warn>.
+ *
+ * Returns the resulting object that represents the given XML node
+ * or the object given to the method as the into parameter.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * into - Optional objec to encode the node into.
+ */
+mxObjectCodec.prototype.decode = function(dec, node, into)
+{
+ var id = node.getAttribute('id');
+ var obj = dec.objects[id];
+
+ if (obj == null)
+ {
+ obj = into || this.cloneTemplate();
+
+ if (id != null)
+ {
+ dec.putObject(id, obj);
+ }
+ }
+
+ node = this.beforeDecode(dec, node, obj);
+ this.decodeNode(dec, node, obj);
+
+ return this.afterDecode(dec, node, obj);
+};
+
+/**
+ * Function: decodeNode
+ *
+ * Calls <decodeAttributes> and <decodeChildren> for the given node.
+ */
+mxObjectCodec.prototype.decodeNode = function(dec, node, obj)
+{
+ if (node != null)
+ {
+ this.decodeAttributes(dec, node, obj);
+ this.decodeChildren(dec, node, obj);
+ }
+};
+
+/**
+ * Function: decodeAttributes
+ *
+ * Decodes all attributes of the given node using <decodeAttribute>.
+ */
+mxObjectCodec.prototype.decodeAttributes = function(dec, node, obj)
+{
+ var attrs = node.attributes;
+
+ if (attrs != null)
+ {
+ for (var i = 0; i < attrs.length; i++)
+ {
+ this.decodeAttribute(dec, attrs[i], obj);
+ }
+ }
+};
+
+/**
+ * Function: decodeAttribute
+ *
+ * Reads the given attribute into the specified object.
+ */
+mxObjectCodec.prototype.decodeAttribute = function(dec, attr, obj)
+{
+ var name = attr.nodeName;
+
+ if (name != 'as' && name != 'id')
+ {
+ // Converts the string true and false to their boolean values.
+ // This may require an additional check on the obj to see if
+ // the existing field is a boolean value or uninitialized, in
+ // which case we may want to convert true and false to a string.
+ var value = this.convertValueFromXml(attr.nodeValue);
+ var fieldname = this.getFieldName(name);
+
+ if (this.isReference(obj, fieldname, value, false))
+ {
+ var tmp = dec.getObject(value);
+
+ if (tmp == null)
+ {
+ mxLog.warn('mxObjectCodec.decode: No object for ' +
+ this.getName() + '.' + name + '=' + value);
+ return; // exit
+ }
+
+ value = tmp;
+ }
+
+ if (!this.isExcluded(obj, name, value, false))
+ {
+ //mxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);
+ obj[name] = value;
+ }
+ }
+};
+
+/**
+ * Function: decodeChildren
+ *
+ * Decodec all children of the given node using <decodeChild>.
+ */
+mxObjectCodec.prototype.decodeChildren = function(dec, node, obj)
+{
+ var child = node.firstChild;
+
+ while (child != null)
+ {
+ var tmp = child.nextSibling;
+
+ if (child.nodeType == mxConstants.NODETYPE_ELEMENT &&
+ !this.processInclude(dec, child, obj))
+ {
+ this.decodeChild(dec, child, obj);
+ }
+
+ child = tmp;
+ }
+};
+
+/**
+ * Function: decodeChild
+ *
+ * Reads the specified child into the given object.
+ */
+mxObjectCodec.prototype.decodeChild = function(dec, child, obj)
+{
+ var fieldname = this.getFieldName(child.getAttribute('as'));
+
+ if (fieldname == null ||
+ !this.isExcluded(obj, fieldname, child, false))
+ {
+ var template = this.getFieldTemplate(obj, fieldname, child);
+ var value = null;
+
+ if (child.nodeName == 'add')
+ {
+ value = child.getAttribute('value');
+
+ if (value == null)
+ {
+ value = mxUtils.eval(mxUtils.getTextContent(child));
+ //mxLog.debug('Decoded '+fieldname+' '+mxUtils.getTextContent(child));
+ }
+ }
+ else
+ {
+ value = dec.decode(child, template);
+ // mxLog.debug('Decoded '+node.nodeName+'.'+fieldname+'='+
+ // ((tmp != null) ? tmp.constructor.name : 'null'));
+ }
+
+ this.addObjectValue(obj, fieldname, value, template);
+ }
+};
+
+/**
+ * Function: getFieldTemplate
+ *
+ * Returns the template instance for the given field. This returns the
+ * value of the field, null if the value is an array or an empty collection
+ * if the value is a collection. The value is then used to populate the
+ * field for a new instance. For strongly typed languages it may be
+ * required to override this to return the correct collection instance
+ * based on the encoded child.
+ */
+mxObjectCodec.prototype.getFieldTemplate = function(obj, fieldname, child)
+{
+ var template = obj[fieldname];
+
+ // Non-empty arrays are replaced completely
+ if (template instanceof Array && template.length > 0)
+ {
+ template = null;
+ }
+
+ return template;
+};
+
+/**
+ * Function: addObjectValue
+ *
+ * Sets the decoded child node as a value of the given object. If the
+ * object is a map, then the value is added with the given fieldname as a
+ * key. If the fieldname is not empty, then setFieldValue is called or
+ * else, if the object is a collection, the value is added to the
+ * collection. For strongly typed languages it may be required to
+ * override this with the correct code to add an entry to an object.
+ */
+mxObjectCodec.prototype.addObjectValue = function(obj, fieldname, value, template)
+{
+ if (value != null && value != template)
+ {
+ if (fieldname != null && fieldname.length > 0)
+ {
+ obj[fieldname] = value;
+ }
+ else
+ {
+ obj.push(value);
+ }
+ //mxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);
+ }
+};
+
+/**
+ * Function: processInclude
+ *
+ * Returns true if the given node is an include directive and
+ * executes the include by decoding the XML document. Returns
+ * false if the given node is not an include directive.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the encoding/decoding process.
+ * node - XML node to be checked.
+ * into - Optional object to pass-thru to the codec.
+ */
+mxObjectCodec.prototype.processInclude = function(dec, node, into)
+{
+ if (node.nodeName == 'include')
+ {
+ var name = node.getAttribute('name');
+
+ if (name != null)
+ {
+ try
+ {
+ var xml = mxUtils.load(name).getDocumentElement();
+
+ if (xml != null)
+ {
+ dec.decode(xml, into);
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: beforeDecode
+ *
+ * Hook for subclassers to pre-process the node for
+ * the specified object and return the node to be
+ * used for further processing by <decode>.
+ * The object is created based on the template in the
+ * calling method and is never null. This implementation
+ * returns the input node. The return value of this
+ * function is used in <decode> to perform
+ * the default decoding into the given object.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Object to encode the node into.
+ */
+mxObjectCodec.prototype.beforeDecode = function(dec, node, obj)
+{
+ return node;
+};
+
+/**
+ * Function: afterDecode
+ *
+ * Hook for subclassers to post-process the object after
+ * decoding. This implementation returns the given object
+ * without any changes. The return value of this method
+ * is returned to the decoder from <decode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * node - XML node to be decoded.
+ * obj - Object that represents the default decoding.
+ */
+mxObjectCodec.prototype.afterDecode = function(dec, node, obj)
+{
+ return obj;
+};