mxCodec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxCodec
  7. *
  8. * XML codec for JavaScript object graphs. See <mxObjectCodec> for a
  9. * description of the general encoding/decoding scheme. This class uses the
  10. * codecs registered in <mxCodecRegistry> for encoding/decoding each object.
  11. *
  12. * References:
  13. *
  14. * In order to resolve references, especially forward references, the mxCodec
  15. * constructor must be given the document that contains the referenced
  16. * elements.
  17. *
  18. * Examples:
  19. *
  20. * The following code is used to encode a graph model.
  21. *
  22. * (code)
  23. * var encoder = new mxCodec();
  24. * var result = encoder.encode(graph.getModel());
  25. * var xml = mxUtils.getXml(result);
  26. * (end)
  27. *
  28. * Example:
  29. *
  30. * Using the code below, an XML document is decoded into an existing model. The
  31. * document may be obtained using one of the functions in mxUtils for loading
  32. * an XML file, eg. <mxUtils.get>, or using <mxUtils.parseXml> for parsing an
  33. * XML string.
  34. *
  35. * (code)
  36. * var doc = mxUtils.parseXml(xmlString);
  37. * var codec = new mxCodec(doc);
  38. * codec.decode(doc.documentElement, graph.getModel());
  39. * (end)
  40. *
  41. * Example:
  42. *
  43. * This example demonstrates parsing a list of isolated cells into an existing
  44. * graph model. Note that the cells do not have a parent reference so they can
  45. * be added anywhere in the cell hierarchy after parsing.
  46. *
  47. * (code)
  48. * var xml = '<root><mxCell id="2" value="Hello," vertex="1"><mxGeometry x="20" y="20" width="80" height="30" as="geometry"/></mxCell><mxCell id="3" value="World!" vertex="1"><mxGeometry x="200" y="150" width="80" height="30" as="geometry"/></mxCell><mxCell id="4" value="" edge="1" source="2" target="3"><mxGeometry relative="1" as="geometry"/></mxCell></root>';
  49. * var doc = mxUtils.parseXml(xml);
  50. * var codec = new mxCodec(doc);
  51. * var elt = doc.documentElement.firstChild;
  52. * var cells = [];
  53. *
  54. * while (elt != null)
  55. * {
  56. * cells.push(codec.decode(elt));
  57. * elt = elt.nextSibling;
  58. * }
  59. *
  60. * graph.addCells(cells);
  61. * (end)
  62. *
  63. * Example:
  64. *
  65. * Using the following code, the selection cells of a graph are encoded and the
  66. * output is displayed in a dialog box.
  67. *
  68. * (code)
  69. * var enc = new mxCodec();
  70. * var cells = graph.getSelectionCells();
  71. * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));
  72. * (end)
  73. *
  74. * Newlines in the XML can be converted to <br>, in which case a '<br>' argument
  75. * must be passed to <mxUtils.getXml> as the second argument.
  76. *
  77. * Debugging:
  78. *
  79. * For debugging I/O you can use the following code to get the sequence of
  80. * encoded objects:
  81. *
  82. * (code)
  83. * var oldEncode = mxCodec.prototype.encode;
  84. * mxCodec.prototype.encode = function(obj)
  85. * {
  86. * mxLog.show();
  87. * mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor));
  88. *
  89. * return oldEncode.apply(this, arguments);
  90. * };
  91. * (end)
  92. *
  93. * Note that the I/O system adds object codecs for new object automatically. For
  94. * decoding those objects, the constructor should be written as follows:
  95. *
  96. * (code)
  97. * var MyObj = function(name)
  98. * {
  99. * // ...
  100. * };
  101. * (end)
  102. *
  103. * Constructor: mxCodec
  104. *
  105. * Constructs an XML encoder/decoder for the specified
  106. * owner document.
  107. *
  108. * Parameters:
  109. *
  110. * document - Optional XML document that contains the data.
  111. * If no document is specified then a new document is created
  112. * using <mxUtils.createXmlDocument>.
  113. */
  114. function mxCodec(document)
  115. {
  116. this.document = document || mxUtils.createXmlDocument();
  117. this.objects = [];
  118. };
  119. /**
  120. * Variable: document
  121. *
  122. * The owner document of the codec.
  123. */
  124. mxCodec.prototype.document = null;
  125. /**
  126. * Variable: objects
  127. *
  128. * Maps from IDs to objects.
  129. */
  130. mxCodec.prototype.objects = null;
  131. /**
  132. * Variable: elements
  133. *
  134. * Lookup table for resolving IDs to elements.
  135. */
  136. mxCodec.prototype.elements = null;
  137. /**
  138. * Variable: encodeDefaults
  139. *
  140. * Specifies if default values should be encoded. Default is false.
  141. */
  142. mxCodec.prototype.encodeDefaults = false;
  143. /**
  144. * Function: putObject
  145. *
  146. * Assoiates the given object with the given ID and returns the given object.
  147. *
  148. * Parameters
  149. *
  150. * id - ID for the object to be associated with.
  151. * obj - Object to be associated with the ID.
  152. */
  153. mxCodec.prototype.putObject = function(id, obj)
  154. {
  155. this.objects[id] = obj;
  156. return obj;
  157. };
  158. /**
  159. * Function: getObject
  160. *
  161. * Returns the decoded object for the element with the specified ID in
  162. * <document>. If the object is not known then <lookup> is used to find an
  163. * object. If no object is found, then the element with the respective ID
  164. * from the document is parsed using <decode>.
  165. */
  166. mxCodec.prototype.getObject = function(id)
  167. {
  168. var obj = null;
  169. if (id != null)
  170. {
  171. obj = this.objects[id];
  172. if (obj == null)
  173. {
  174. obj = this.lookup(id);
  175. if (obj == null)
  176. {
  177. var node = this.getElementById(id);
  178. if (node != null)
  179. {
  180. obj = this.decode(node);
  181. }
  182. }
  183. }
  184. }
  185. return obj;
  186. };
  187. /**
  188. * Function: lookup
  189. *
  190. * Hook for subclassers to implement a custom lookup mechanism for cell IDs.
  191. * This implementation always returns null.
  192. *
  193. * Example:
  194. *
  195. * (code)
  196. * var codec = new mxCodec();
  197. * codec.lookup = function(id)
  198. * {
  199. * return model.getCell(id);
  200. * };
  201. * (end)
  202. *
  203. * Parameters:
  204. *
  205. * id - ID of the object to be returned.
  206. */
  207. mxCodec.prototype.lookup = function(id)
  208. {
  209. return null;
  210. };
  211. /**
  212. * Function: getElementById
  213. *
  214. * Returns the element with the given ID from <document>.
  215. *
  216. * Parameters:
  217. *
  218. * id - String that contains the ID.
  219. */
  220. mxCodec.prototype.getElementById = function(id)
  221. {
  222. this.updateElements();
  223. return this.elements[id];
  224. };
  225. /**
  226. * Function: updateElements
  227. *
  228. * Returns the element with the given ID from <document>.
  229. *
  230. * Parameters:
  231. *
  232. * id - String that contains the ID.
  233. */
  234. mxCodec.prototype.updateElements = function()
  235. {
  236. if (this.elements == null)
  237. {
  238. this.elements = new Object();
  239. if (this.document.documentElement != null)
  240. {
  241. this.addElement(this.document.documentElement);
  242. }
  243. }
  244. };
  245. /**
  246. * Function: addElement
  247. *
  248. * Adds the given element to <elements> if it has an ID.
  249. */
  250. mxCodec.prototype.addElement = function(node)
  251. {
  252. if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
  253. {
  254. var id = node.getAttribute('id');
  255. if (id != null)
  256. {
  257. if (this.elements[id] == null)
  258. {
  259. this.elements[id] = node;
  260. }
  261. else if (this.elements[id] != node)
  262. {
  263. throw new Error(id + ': Duplicate ID');
  264. }
  265. }
  266. }
  267. node = node.firstChild;
  268. while (node != null)
  269. {
  270. this.addElement(node);
  271. node = node.nextSibling;
  272. }
  273. };
  274. /**
  275. * Function: getId
  276. *
  277. * Returns the ID of the specified object. This implementation
  278. * calls <reference> first and if that returns null handles
  279. * the object as an <mxCell> by returning their IDs using
  280. * <mxCell.getId>. If no ID exists for the given cell, then
  281. * an on-the-fly ID is generated using <mxCellPath.create>.
  282. *
  283. * Parameters:
  284. *
  285. * obj - Object to return the ID for.
  286. */
  287. mxCodec.prototype.getId = function(obj)
  288. {
  289. var id = null;
  290. if (obj != null)
  291. {
  292. id = this.reference(obj);
  293. if (id == null && obj instanceof mxCell)
  294. {
  295. id = obj.getId();
  296. if (id == null)
  297. {
  298. // Uses an on-the-fly Id
  299. id = mxCellPath.create(obj);
  300. if (id.length == 0)
  301. {
  302. id = 'root';
  303. }
  304. }
  305. }
  306. }
  307. return id;
  308. };
  309. /**
  310. * Function: reference
  311. *
  312. * Hook for subclassers to implement a custom method
  313. * for retrieving IDs from objects. This implementation
  314. * always returns null.
  315. *
  316. * Example:
  317. *
  318. * (code)
  319. * var codec = new mxCodec();
  320. * codec.reference = function(obj)
  321. * {
  322. * return obj.getCustomId();
  323. * };
  324. * (end)
  325. *
  326. * Parameters:
  327. *
  328. * obj - Object whose ID should be returned.
  329. */
  330. mxCodec.prototype.reference = function(obj)
  331. {
  332. return null;
  333. };
  334. /**
  335. * Function: encode
  336. *
  337. * Encodes the specified object and returns the resulting
  338. * XML node.
  339. *
  340. * Parameters:
  341. *
  342. * obj - Object to be encoded.
  343. */
  344. mxCodec.prototype.encode = function(obj)
  345. {
  346. var node = null;
  347. if (obj != null && obj.constructor != null)
  348. {
  349. var enc = mxCodecRegistry.getCodec(obj.constructor);
  350. if (enc != null)
  351. {
  352. node = enc.encode(this, obj);
  353. }
  354. else
  355. {
  356. if (mxUtils.isNode(obj))
  357. {
  358. node = mxUtils.importNode(this.document, obj, true);
  359. }
  360. else
  361. {
  362. mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor));
  363. }
  364. }
  365. }
  366. return node;
  367. };
  368. /**
  369. * Function: decode
  370. *
  371. * Decodes the given XML node. The optional "into"
  372. * argument specifies an existing object to be
  373. * used. If no object is given, then a new instance
  374. * is created using the constructor from the codec.
  375. *
  376. * The function returns the passed in object or
  377. * the new instance if no object was given.
  378. *
  379. * Parameters:
  380. *
  381. * node - XML node to be decoded.
  382. * into - Optional object to be decodec into.
  383. */
  384. mxCodec.prototype.decode = function(node, into)
  385. {
  386. this.updateElements();
  387. var obj = null;
  388. if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
  389. {
  390. var ctor = null;
  391. try
  392. {
  393. ctor = window[node.nodeName];
  394. }
  395. catch (err)
  396. {
  397. // ignore
  398. }
  399. var dec = mxCodecRegistry.getCodec(ctor);
  400. if (dec != null)
  401. {
  402. obj = dec.decode(this, node, into);
  403. }
  404. else
  405. {
  406. obj = node.cloneNode(true);
  407. obj.removeAttribute('as');
  408. }
  409. }
  410. return obj;
  411. };
  412. /**
  413. * Function: encodeCell
  414. *
  415. * Encoding of cell hierarchies is built-into the core, but
  416. * is a higher-level function that needs to be explicitely
  417. * used by the respective object encoders (eg. <mxModelCodec>,
  418. * <mxChildChangeCodec> and <mxRootChangeCodec>). This
  419. * implementation writes the given cell and its children as a
  420. * (flat) sequence into the given node. The children are not
  421. * encoded if the optional includeChildren is false. The
  422. * function is in charge of adding the result into the
  423. * given node and has no return value.
  424. *
  425. * Parameters:
  426. *
  427. * cell - <mxCell> to be encoded.
  428. * node - Parent XML node to add the encoded cell into.
  429. * includeChildren - Optional boolean indicating if the
  430. * function should include all descendents. Default is true.
  431. */
  432. mxCodec.prototype.encodeCell = function(cell, node, includeChildren)
  433. {
  434. node.appendChild(this.encode(cell));
  435. if (includeChildren == null || includeChildren)
  436. {
  437. var childCount = cell.getChildCount();
  438. for (var i = 0; i < childCount; i++)
  439. {
  440. this.encodeCell(cell.getChildAt(i), node);
  441. }
  442. }
  443. };
  444. /**
  445. * Function: isCellCodec
  446. *
  447. * Returns true if the given codec is a cell codec. This uses
  448. * <mxCellCodec.isCellCodec> to check if the codec is of the
  449. * given type.
  450. */
  451. mxCodec.prototype.isCellCodec = function(codec)
  452. {
  453. if (codec != null && typeof(codec.isCellCodec) == 'function')
  454. {
  455. return codec.isCellCodec();
  456. }
  457. return false;
  458. };
  459. /**
  460. * Function: decodeCell
  461. *
  462. * Decodes cells that have been encoded using inversion, ie.
  463. * where the user object is the enclosing node in the XML,
  464. * and restores the group and graph structure in the cells.
  465. * Returns a new <mxCell> instance that represents the
  466. * given node.
  467. *
  468. * Parameters:
  469. *
  470. * node - XML node that contains the cell data.
  471. * restoreStructures - Optional boolean indicating whether
  472. * the graph structure should be restored by calling insert
  473. * and insertEdge on the parent and terminals, respectively.
  474. * Default is true.
  475. */
  476. mxCodec.prototype.decodeCell = function(node, restoreStructures)
  477. {
  478. restoreStructures = (restoreStructures != null) ? restoreStructures : true;
  479. var cell = null;
  480. if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
  481. {
  482. // Tries to find a codec for the given node name. If that does
  483. // not return a codec then the node is the user object (an XML node
  484. // that contains the mxCell, aka inversion).
  485. var decoder = mxCodecRegistry.getCodec(node.nodeName);
  486. // Tries to find the codec for the cell inside the user object.
  487. // This assumes all node names inside the user object are either
  488. // not registered or they correspond to a class for cells.
  489. if (!this.isCellCodec(decoder))
  490. {
  491. var child = node.firstChild;
  492. while (child != null && !this.isCellCodec(decoder))
  493. {
  494. decoder = mxCodecRegistry.getCodec(child.nodeName);
  495. child = child.nextSibling;
  496. }
  497. }
  498. if (!this.isCellCodec(decoder))
  499. {
  500. decoder = mxCodecRegistry.getCodec(mxCell);
  501. }
  502. cell = decoder.decode(this, node);
  503. if (restoreStructures)
  504. {
  505. this.insertIntoGraph(cell);
  506. }
  507. }
  508. return cell;
  509. };
  510. /**
  511. * Function: insertIntoGraph
  512. *
  513. * Inserts the given cell into its parent and terminal cells.
  514. */
  515. mxCodec.prototype.insertIntoGraph = function(cell)
  516. {
  517. var parent = cell.parent;
  518. var source = cell.getTerminal(true);
  519. var target = cell.getTerminal(false);
  520. // Fixes possible inconsistencies during insert into graph
  521. cell.setTerminal(null, false);
  522. cell.setTerminal(null, true);
  523. cell.parent = null;
  524. if (parent != null)
  525. {
  526. if (parent == cell)
  527. {
  528. throw new Error(parent.id + ': Self Reference');
  529. }
  530. else
  531. {
  532. parent.insert(cell);
  533. }
  534. }
  535. if (source != null)
  536. {
  537. source.insertEdge(cell, true);
  538. }
  539. if (target != null)
  540. {
  541. target.insertEdge(cell, false);
  542. }
  543. };
  544. /**
  545. * Function: setAttribute
  546. *
  547. * Sets the attribute on the specified node to value. This is a
  548. * helper method that makes sure the attribute and value arguments
  549. * are not null.
  550. *
  551. * Parameters:
  552. *
  553. * node - XML node to set the attribute for.
  554. * attributes - Attributename to be set.
  555. * value - New value of the attribute.
  556. */
  557. mxCodec.prototype.setAttribute = function(node, attribute, value)
  558. {
  559. if (attribute != null && value != null)
  560. {
  561. node.setAttribute(attribute, value);
  562. }
  563. };
  564. __mxOutput.mxCodec = typeof mxCodec !== 'undefined' ? mxCodec : undefined;