/** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxCodec * * XML codec for JavaScript object graphs. See for a * description of the general encoding/decoding scheme. This class uses the * codecs registered in for encoding/decoding each object. * * References: * * In order to resolve references, especially forward references, the mxCodec * constructor must be given the document that contains the referenced * elements. * * Examples: * * The following code is used to encode a graph model. * * (code) * var encoder = new mxCodec(); * var result = encoder.encode(graph.getModel()); * var xml = mxUtils.getXml(result); * (end) * * Example: * * Using the code below, an XML document is decoded into an existing model. The * document may be obtained using one of the functions in mxUtils for loading * an XML file, eg. , or using for parsing an * XML string. * * (code) * var doc = mxUtils.parseXml(xmlString); * var codec = new mxCodec(doc); * codec.decode(doc.documentElement, graph.getModel()); * (end) * * Example: * * This example demonstrates parsing a list of isolated cells into an existing * graph model. Note that the cells do not have a parent reference so they can * be added anywhere in the cell hierarchy after parsing. * * (code) * var xml = ''; * var doc = mxUtils.parseXml(xml); * var codec = new mxCodec(doc); * var elt = doc.documentElement.firstChild; * var cells = []; * * while (elt != null) * { * cells.push(codec.decode(elt)); * elt = elt.nextSibling; * } * * graph.addCells(cells); * (end) * * Example: * * Using the following code, the selection cells of a graph are encoded and the * output is displayed in a dialog box. * * (code) * var enc = new mxCodec(); * var cells = graph.getSelectionCells(); * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells))); * (end) * * Newlines in the XML can be converted to
, in which case a '
' argument * must be passed to as the second argument. * * Debugging: * * For debugging I/O you can use the following code to get the sequence of * encoded objects: * * (code) * var oldEncode = mxCodec.prototype.encode; * mxCodec.prototype.encode = function(obj) * { * mxLog.show(); * mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor)); * * return oldEncode.apply(this, arguments); * }; * (end) * * Note that the I/O system adds object codecs for new object automatically. For * decoding those objects, the constructor should be written as follows: * * (code) * var MyObj = function(name) * { * // ... * }; * (end) * * Constructor: mxCodec * * Constructs an XML encoder/decoder for the specified * owner document. * * Parameters: * * document - Optional XML document that contains the data. * If no document is specified then a new document is created * using . */ function mxCodec(document) { this.document = document || mxUtils.createXmlDocument(); this.objects = []; }; /** * Variable: document * * The owner document of the codec. */ mxCodec.prototype.document = null; /** * Variable: objects * * Maps from IDs to objects. */ mxCodec.prototype.objects = null; /** * Variable: elements * * Lookup table for resolving IDs to elements. */ mxCodec.prototype.elements = null; /** * Variable: encodeDefaults * * Specifies if default values should be encoded. Default is false. */ mxCodec.prototype.encodeDefaults = false; /** * Function: putObject * * Assoiates the given object with the given ID and returns the given object. * * Parameters * * id - ID for the object to be associated with. * obj - Object to be associated with the ID. */ mxCodec.prototype.putObject = function(id, obj) { this.objects[id] = obj; return obj; }; /** * Function: getObject * * Returns the decoded object for the element with the specified ID in * . If the object is not known then is used to find an * object. If no object is found, then the element with the respective ID * from the document is parsed using . */ mxCodec.prototype.getObject = function(id) { var obj = null; if (id != null) { obj = this.objects[id]; if (obj == null) { obj = this.lookup(id); if (obj == null) { var node = this.getElementById(id); if (node != null) { obj = this.decode(node); } } } } return obj; }; /** * Function: lookup * * Hook for subclassers to implement a custom lookup mechanism for cell IDs. * This implementation always returns null. * * Example: * * (code) * var codec = new mxCodec(); * codec.lookup = function(id) * { * return model.getCell(id); * }; * (end) * * Parameters: * * id - ID of the object to be returned. */ mxCodec.prototype.lookup = function(id) { return null; }; /** * Function: getElementById * * Returns the element with the given ID from . * * Parameters: * * id - String that contains the ID. */ mxCodec.prototype.getElementById = function(id) { this.updateElements(); return this.elements[id]; }; /** * Function: updateElements * * Returns the element with the given ID from . * * Parameters: * * id - String that contains the ID. */ mxCodec.prototype.updateElements = function() { if (this.elements == null) { this.elements = new Object(); if (this.document.documentElement != null) { this.addElement(this.document.documentElement); } } }; /** * Function: addElement * * Adds the given element to if it has an ID. */ mxCodec.prototype.addElement = function(node) { if (node.nodeType == mxConstants.NODETYPE_ELEMENT) { var id = node.getAttribute('id'); if (id != null) { if (this.elements[id] == null) { this.elements[id] = node; } else if (this.elements[id] != node) { throw new Error(id + ': Duplicate ID'); } } } node = node.firstChild; while (node != null) { this.addElement(node); node = node.nextSibling; } }; /** * Function: getId * * Returns the ID of the specified object. This implementation * calls first and if that returns null handles * the object as an by returning their IDs using * . If no ID exists for the given cell, then * an on-the-fly ID is generated using . * * Parameters: * * obj - Object to return the ID for. */ mxCodec.prototype.getId = function(obj) { var id = null; if (obj != null) { id = this.reference(obj); if (id == null && obj instanceof mxCell) { id = obj.getId(); if (id == null) { // Uses an on-the-fly Id id = mxCellPath.create(obj); if (id.length == 0) { id = 'root'; } } } } return id; }; /** * Function: reference * * Hook for subclassers to implement a custom method * for retrieving IDs from objects. This implementation * always returns null. * * Example: * * (code) * var codec = new mxCodec(); * codec.reference = function(obj) * { * return obj.getCustomId(); * }; * (end) * * Parameters: * * obj - Object whose ID should be returned. */ mxCodec.prototype.reference = function(obj) { return null; }; /** * Function: encode * * Encodes the specified object and returns the resulting * XML node. * * Parameters: * * obj - Object to be encoded. */ mxCodec.prototype.encode = function(obj) { var node = null; if (obj != null && obj.constructor != null) { var enc = mxCodecRegistry.getCodec(obj.constructor); if (enc != null) { node = enc.encode(this, obj); } else { if (mxUtils.isNode(obj)) { node = mxUtils.importNode(this.document, obj, true); } else { mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor)); } } } return node; }; /** * Function: decode * * Decodes the given XML node. The optional "into" * argument specifies an existing object to be * used. If no object is given, then a new instance * is created using the constructor from the codec. * * The function returns the passed in object or * the new instance if no object was given. * * Parameters: * * node - XML node to be decoded. * into - Optional object to be decodec into. */ mxCodec.prototype.decode = function(node, into) { this.updateElements(); var obj = null; if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT) { var ctor = null; try { ctor = window[node.nodeName]; } catch (err) { // ignore } var dec = mxCodecRegistry.getCodec(ctor); if (dec != null) { obj = dec.decode(this, node, into); } else { obj = node.cloneNode(true); obj.removeAttribute('as'); } } return obj; }; /** * Function: encodeCell * * Encoding of cell hierarchies is built-into the core, but * is a higher-level function that needs to be explicitely * used by the respective object encoders (eg. , * and ). This * implementation writes the given cell and its children as a * (flat) sequence into the given node. The children are not * encoded if the optional includeChildren is false. The * function is in charge of adding the result into the * given node and has no return value. * * Parameters: * * cell - to be encoded. * node - Parent XML node to add the encoded cell into. * includeChildren - Optional boolean indicating if the * function should include all descendents. Default is true. */ mxCodec.prototype.encodeCell = function(cell, node, includeChildren) { node.appendChild(this.encode(cell)); if (includeChildren == null || includeChildren) { var childCount = cell.getChildCount(); for (var i = 0; i < childCount; i++) { this.encodeCell(cell.getChildAt(i), node); } } }; /** * Function: isCellCodec * * Returns true if the given codec is a cell codec. This uses * to check if the codec is of the * given type. */ mxCodec.prototype.isCellCodec = function(codec) { if (codec != null && typeof(codec.isCellCodec) == 'function') { return codec.isCellCodec(); } return false; }; /** * Function: decodeCell * * Decodes cells that have been encoded using inversion, ie. * where the user object is the enclosing node in the XML, * and restores the group and graph structure in the cells. * Returns a new instance that represents the * given node. * * Parameters: * * node - XML node that contains the cell data. * restoreStructures - Optional boolean indicating whether * the graph structure should be restored by calling insert * and insertEdge on the parent and terminals, respectively. * Default is true. */ mxCodec.prototype.decodeCell = function(node, restoreStructures) { restoreStructures = (restoreStructures != null) ? restoreStructures : true; var cell = null; if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT) { // Tries to find a codec for the given node name. If that does // not return a codec then the node is the user object (an XML node // that contains the mxCell, aka inversion). var decoder = mxCodecRegistry.getCodec(node.nodeName); // Tries to find the codec for the cell inside the user object. // This assumes all node names inside the user object are either // not registered or they correspond to a class for cells. if (!this.isCellCodec(decoder)) { var child = node.firstChild; while (child != null && !this.isCellCodec(decoder)) { decoder = mxCodecRegistry.getCodec(child.nodeName); child = child.nextSibling; } } if (!this.isCellCodec(decoder)) { decoder = mxCodecRegistry.getCodec(mxCell); } cell = decoder.decode(this, node); if (restoreStructures) { this.insertIntoGraph(cell); } } return cell; }; /** * Function: insertIntoGraph * * Inserts the given cell into its parent and terminal cells. */ mxCodec.prototype.insertIntoGraph = function(cell) { var parent = cell.parent; var source = cell.getTerminal(true); var target = cell.getTerminal(false); // Fixes possible inconsistencies during insert into graph cell.setTerminal(null, false); cell.setTerminal(null, true); cell.parent = null; if (parent != null) { if (parent == cell) { throw new Error(parent.id + ': Self Reference'); } else { parent.insert(cell); } } if (source != null) { source.insertEdge(cell, true); } if (target != null) { target.insertEdge(cell, false); } }; /** * Function: setAttribute * * Sets the attribute on the specified node to value. This is a * helper method that makes sure the attribute and value arguments * are not null. * * Parameters: * * node - XML node to set the attribute for. * attributes - Attributename to be set. * value - New value of the attribute. */ mxCodec.prototype.setAttribute = function(node, attribute, value) { if (attribute != null && value != null) { node.setAttribute(attribute, value); } };