mxCodec.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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: allowlist
  121. *
  122. * Array of strings that specifies the types to be decoded. Null means all
  123. * types are allowed. Default is null.
  124. */
  125. mxCodec.allowlist = null;
  126. /**
  127. * Variable: document
  128. *
  129. * The owner document of the codec.
  130. */
  131. mxCodec.prototype.document = null;
  132. /**
  133. * Variable: objects
  134. *
  135. * Maps from IDs to objects.
  136. */
  137. mxCodec.prototype.objects = null;
  138. /**
  139. * Variable: elements
  140. *
  141. * Lookup table for resolving IDs to elements.
  142. */
  143. mxCodec.prototype.elements = null;
  144. /**
  145. * Variable: encodeDefaults
  146. *
  147. * Specifies if default values should be encoded. Default is false.
  148. */
  149. mxCodec.prototype.encodeDefaults = false;
  150. /**
  151. * Function: putObject
  152. *
  153. * Assoiates the given object with the given ID and returns the given object.
  154. *
  155. * Parameters
  156. *
  157. * id - ID for the object to be associated with.
  158. * obj - Object to be associated with the ID.
  159. */
  160. mxCodec.prototype.putObject = function(id, obj)
  161. {
  162. this.objects[id] = obj;
  163. return obj;
  164. };
  165. /**
  166. * Function: getObject
  167. *
  168. * Returns the decoded object for the element with the specified ID in
  169. * <document>. If the object is not known then <lookup> is used to find an
  170. * object. If no object is found, then the element with the respective ID
  171. * from the document is parsed using <decode>.
  172. */
  173. mxCodec.prototype.getObject = function(id)
  174. {
  175. var obj = null;
  176. if (id != null)
  177. {
  178. obj = this.objects[id];
  179. if (obj == null)
  180. {
  181. obj = this.lookup(id);
  182. if (obj == null)
  183. {
  184. var node = this.getElementById(id);
  185. if (node != null)
  186. {
  187. obj = this.decode(node);
  188. }
  189. }
  190. }
  191. }
  192. return obj;
  193. };
  194. /**
  195. * Function: lookup
  196. *
  197. * Hook for subclassers to implement a custom lookup mechanism for cell IDs.
  198. * This implementation always returns null.
  199. *
  200. * Example:
  201. *
  202. * (code)
  203. * var codec = new mxCodec();
  204. * codec.lookup = function(id)
  205. * {
  206. * return model.getCell(id);
  207. * };
  208. * (end)
  209. *
  210. * Parameters:
  211. *
  212. * id - ID of the object to be returned.
  213. */
  214. mxCodec.prototype.lookup = function(id)
  215. {
  216. return null;
  217. };
  218. /**
  219. * Function: getElementById
  220. *
  221. * Returns the element with the given ID from <document>.
  222. *
  223. * Parameters:
  224. *
  225. * id - String that contains the ID.
  226. */
  227. mxCodec.prototype.getElementById = function(id)
  228. {
  229. this.updateElements();
  230. return this.elements[id];
  231. };
  232. /**
  233. * Function: updateElements
  234. *
  235. * Returns the element with the given ID from <document>.
  236. *
  237. * Parameters:
  238. *
  239. * id - String that contains the ID.
  240. */
  241. mxCodec.prototype.updateElements = function()
  242. {
  243. if (this.elements == null)
  244. {
  245. this.elements = new Object();
  246. if (this.document.documentElement != null)
  247. {
  248. this.addElement(this.document.documentElement);
  249. }
  250. }
  251. };
  252. /**
  253. * Function: addElement
  254. *
  255. * Adds the given element to <elements> if it has an ID.
  256. */
  257. mxCodec.prototype.addElement = function(node)
  258. {
  259. if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
  260. {
  261. var id = node.getAttribute('id');
  262. if (id != null)
  263. {
  264. if (this.elements[id] == null)
  265. {
  266. this.elements[id] = node;
  267. }
  268. else if (this.elements[id] != node)
  269. {
  270. throw new Error(id + ': Duplicate ID');
  271. }
  272. }
  273. }
  274. node = node.firstChild;
  275. while (node != null)
  276. {
  277. this.addElement(node);
  278. node = node.nextSibling;
  279. }
  280. };
  281. /**
  282. * Function: isObjectIgnored
  283. *
  284. * Returns true if the given object is ignored by the codec. This
  285. * implementation returns false if the given object is not null.
  286. */
  287. mxCodec.prototype.isObjectIgnored = function(obj)
  288. {
  289. return obj == null;
  290. };
  291. /**
  292. * Function: getId
  293. *
  294. * Returns the ID of the specified object. This implementation
  295. * calls <reference> first and if that returns null handles
  296. * the object as an <mxCell> by returning their IDs using
  297. * <mxCell.getId>. If no ID exists for the given cell, then
  298. * an on-the-fly ID is generated using <mxCellPath.create>.
  299. *
  300. * Parameters:
  301. *
  302. * obj - Object to return the ID for.
  303. */
  304. mxCodec.prototype.getId = function(obj)
  305. {
  306. var id = null;
  307. if (obj != null && !this.isObjectIgnored(obj))
  308. {
  309. id = this.reference(obj);
  310. if (id == null && obj instanceof mxCell)
  311. {
  312. id = obj.getId();
  313. if (id == null)
  314. {
  315. // Uses an on-the-fly Id
  316. id = mxCellPath.create(obj);
  317. if (id.length == 0)
  318. {
  319. id = 'root';
  320. }
  321. }
  322. }
  323. }
  324. return id;
  325. };
  326. /**
  327. * Function: reference
  328. *
  329. * Hook for subclassers to implement a custom method
  330. * for retrieving IDs from objects. This implementation
  331. * always returns null.
  332. *
  333. * Example:
  334. *
  335. * (code)
  336. * var codec = new mxCodec();
  337. * codec.reference = function(obj)
  338. * {
  339. * return obj.getCustomId();
  340. * };
  341. * (end)
  342. *
  343. * Parameters:
  344. *
  345. * obj - Object whose ID should be returned.
  346. */
  347. mxCodec.prototype.reference = function(obj)
  348. {
  349. return null;
  350. };
  351. /**
  352. * Function: encode
  353. *
  354. * Encodes the specified object and returns the resulting
  355. * XML node.
  356. *
  357. * Parameters:
  358. *
  359. * obj - Object to be encoded.
  360. */
  361. mxCodec.prototype.encode = function(obj)
  362. {
  363. var node = null;
  364. if (obj != null && obj.constructor != null && !this.isObjectIgnored(obj))
  365. {
  366. var enc = mxCodecRegistry.getCodec(obj.constructor);
  367. if (enc != null)
  368. {
  369. node = enc.encode(this, obj);
  370. }
  371. else
  372. {
  373. if (mxUtils.isNode(obj))
  374. {
  375. node = mxUtils.importNode(this.document, obj, true);
  376. }
  377. else
  378. {
  379. mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor));
  380. }
  381. }
  382. }
  383. return node;
  384. };
  385. /**
  386. * Function: decode
  387. *
  388. * Decodes the given XML node. The optional "into"
  389. * argument specifies an existing object to be
  390. * used. If no object is given, then a new instance
  391. * is created using the constructor from the codec.
  392. *
  393. * The function returns the passed in object or
  394. * the new instance if no object was given.
  395. *
  396. * Parameters:
  397. *
  398. * node - XML node to be decoded.
  399. * into - Optional object to be decodec into.
  400. */
  401. mxCodec.prototype.decode = function(node, into)
  402. {
  403. this.updateElements();
  404. var obj = null;
  405. if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
  406. {
  407. var ctor = this.getConstructor(node.nodeName);
  408. var dec = mxCodecRegistry.getCodec(ctor);
  409. if (dec != null)
  410. {
  411. obj = dec.decode(this, node, into);
  412. }
  413. else
  414. {
  415. obj = node.cloneNode(true);
  416. obj.removeAttribute('as');
  417. }
  418. }
  419. return obj;
  420. };
  421. /**
  422. * Function: isConstructorAllowed
  423. *
  424. * Returns true if the given constructor name is allowed to be
  425. * instantiated.
  426. *
  427. * Parameters:
  428. *
  429. * name - Name of the constructor to be checked.
  430. */
  431. mxCodec.prototype.isConstructorAllowed = function(name)
  432. {
  433. return mxCodec.allowlist == null || mxUtils.indexOf(
  434. mxCodec.allowlist, name) >= 0;
  435. };
  436. /**
  437. * Function: getConstructor
  438. *
  439. * Returns the constructor for the given object type.
  440. *
  441. * Parameters:
  442. *
  443. * name - Name of the type to be returned.
  444. */
  445. mxCodec.prototype.getConstructor = function(name)
  446. {
  447. var ctor = null;
  448. try
  449. {
  450. if (this.isConstructorAllowed(name))
  451. {
  452. ctor = window[name];
  453. }
  454. }
  455. catch (err)
  456. {
  457. // ignore
  458. }
  459. return ctor;
  460. };
  461. /**
  462. * Function: encodeCell
  463. *
  464. * Encoding of cell hierarchies is built-into the core, but
  465. * is a higher-level function that needs to be explicitely
  466. * used by the respective object encoders (eg. <mxModelCodec>,
  467. * <mxChildChangeCodec> and <mxRootChangeCodec>). This
  468. * implementation writes the given cell and its children as a
  469. * (flat) sequence into the given node. The children are not
  470. * encoded if the optional includeChildren is false. The
  471. * function is in charge of adding the result into the
  472. * given node and has no return value.
  473. *
  474. * Parameters:
  475. *
  476. * cell - <mxCell> to be encoded.
  477. * node - Parent XML node to add the encoded cell into.
  478. * includeChildren - Optional boolean indicating if the
  479. * function should include all descendents. Default is true.
  480. */
  481. mxCodec.prototype.encodeCell = function(cell, node, includeChildren)
  482. {
  483. if (!this.isObjectIgnored(cell))
  484. {
  485. var cellNode = this.encode(cell);
  486. if (cellNode != null)
  487. {
  488. node.appendChild(cellNode);
  489. }
  490. if (includeChildren == null || includeChildren)
  491. {
  492. var childCount = cell.getChildCount();
  493. for (var i = 0; i < childCount; i++)
  494. {
  495. this.encodeCell(cell.getChildAt(i), node);
  496. }
  497. }
  498. }
  499. };
  500. /**
  501. * Function: isCellCodec
  502. *
  503. * Returns true if the given codec is a cell codec. This uses
  504. * <mxCellCodec.isCellCodec> to check if the codec is of the
  505. * given type.
  506. */
  507. mxCodec.prototype.isCellCodec = function(codec)
  508. {
  509. if (codec != null && typeof(codec.isCellCodec) == 'function')
  510. {
  511. return codec.isCellCodec();
  512. }
  513. return false;
  514. };
  515. /**
  516. * Function: decodeCell
  517. *
  518. * Decodes cells that have been encoded using inversion, ie.
  519. * where the user object is the enclosing node in the XML,
  520. * and restores the group and graph structure in the cells.
  521. * Returns a new <mxCell> instance that represents the
  522. * given node.
  523. *
  524. * Parameters:
  525. *
  526. * node - XML node that contains the cell data.
  527. * restoreStructures - Optional boolean indicating whether
  528. * the graph structure should be restored by calling insert
  529. * and insertEdge on the parent and terminals, respectively.
  530. * Default is true.
  531. */
  532. mxCodec.prototype.decodeCell = function(node, restoreStructures)
  533. {
  534. restoreStructures = (restoreStructures != null) ? restoreStructures : true;
  535. var cell = null;
  536. if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
  537. {
  538. // Tries to find a codec for the given node name. If that does
  539. // not return a codec then the node is the user object (an XML node
  540. // that contains the mxCell, aka inversion).
  541. var decoder = mxCodecRegistry.getCodec(node.nodeName);
  542. // Tries to find the codec for the cell inside the user object.
  543. // This assumes all node names inside the user object are either
  544. // not registered or they correspond to a class for cells.
  545. if (!this.isCellCodec(decoder))
  546. {
  547. var child = node.firstChild;
  548. while (child != null && !this.isCellCodec(decoder))
  549. {
  550. decoder = mxCodecRegistry.getCodec(child.nodeName);
  551. child = child.nextSibling;
  552. }
  553. }
  554. if (!this.isCellCodec(decoder))
  555. {
  556. decoder = mxCodecRegistry.getCodec(mxCell);
  557. }
  558. cell = decoder.decode(this, node);
  559. if (restoreStructures)
  560. {
  561. this.insertIntoGraph(cell);
  562. }
  563. }
  564. return cell;
  565. };
  566. /**
  567. * Function: insertIntoGraph
  568. *
  569. * Inserts the given cell into its parent and terminal cells.
  570. */
  571. mxCodec.prototype.insertIntoGraph = function(cell)
  572. {
  573. var parent = cell.parent;
  574. var source = cell.getTerminal(true);
  575. var target = cell.getTerminal(false);
  576. // Fixes possible inconsistencies during insert into graph
  577. cell.setTerminal(null, false);
  578. cell.setTerminal(null, true);
  579. cell.parent = null;
  580. if (parent != null)
  581. {
  582. if (parent == cell)
  583. {
  584. throw new Error(parent.id + ': Self Reference');
  585. }
  586. else
  587. {
  588. parent.insert(cell);
  589. }
  590. }
  591. if (source != null)
  592. {
  593. source.insertEdge(cell, true);
  594. }
  595. if (target != null)
  596. {
  597. target.insertEdge(cell, false);
  598. }
  599. };
  600. /**
  601. * Function: setAttribute
  602. *
  603. * Sets the attribute on the specified node to value. This is a
  604. * helper method that makes sure the attribute and value arguments
  605. * are not null.
  606. *
  607. * Parameters:
  608. *
  609. * node - XML node to set the attribute for.
  610. * attributes - Attributename to be set.
  611. * value - New value of the attribute.
  612. */
  613. mxCodec.prototype.setAttribute = function(node, attribute, value)
  614. {
  615. if (attribute != null && value != null)
  616. {
  617. node.setAttribute(attribute, value);
  618. }
  619. };