GliffyDiagramConverter.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. /**
  2. * Copyright (c) 2006-2016, JGraph Ltd
  3. * Copyright (c) 2006-2016, Gaudenz Alder
  4. */
  5. package com.mxgraph.io.gliffy.importer;
  6. import java.io.UnsupportedEncodingException;
  7. import java.util.ArrayList;
  8. import java.util.Collection;
  9. import java.util.Collections;
  10. import java.util.Comparator;
  11. import java.util.LinkedHashMap;
  12. import java.util.List;
  13. import java.util.Map;
  14. import java.util.logging.Logger;
  15. import java.util.regex.Matcher;
  16. import java.util.regex.Pattern;
  17. import org.w3c.dom.Document;
  18. import org.w3c.dom.Element;
  19. import com.google.gson.GsonBuilder;
  20. import com.mxgraph.io.mxCodec;
  21. import com.mxgraph.io.gliffy.model.Constraint;
  22. import com.mxgraph.io.gliffy.model.Constraint.ConstraintData;
  23. import com.mxgraph.io.gliffy.model.Constraints;
  24. import com.mxgraph.io.gliffy.model.Diagram;
  25. import com.mxgraph.io.gliffy.model.EmbeddedResources.Resource;
  26. import com.mxgraph.io.gliffy.model.GliffyObject;
  27. import com.mxgraph.io.gliffy.model.GliffyText;
  28. import com.mxgraph.io.gliffy.model.Graphic;
  29. import com.mxgraph.io.gliffy.model.Graphic.GliffyImage;
  30. import com.mxgraph.io.gliffy.model.Graphic.GliffyLine;
  31. import com.mxgraph.io.gliffy.model.Graphic.GliffyMindmap;
  32. import com.mxgraph.io.gliffy.model.Graphic.GliffyShape;
  33. import com.mxgraph.io.gliffy.model.Graphic.GliffySvg;
  34. import com.mxgraph.model.mxCell;
  35. import com.mxgraph.model.mxGeometry;
  36. import com.mxgraph.online.Utils;
  37. import com.mxgraph.util.mxDomUtils;
  38. import com.mxgraph.util.mxPoint;
  39. import com.mxgraph.util.mxUtils;
  40. import com.mxgraph.util.mxXmlUtils;
  41. import com.mxgraph.view.mxGraphHeadless;
  42. /**
  43. * Performs a conversion of a Gliffy diagram into a Draw.io diagram
  44. * <p>
  45. * Example :
  46. * <p>
  47. * <code><i>
  48. * GliffyDiagramConverter converter = new GliffyDiagramConverter(gliffyJsonString);<br>
  49. * String drawioXml = converter.getGraphXml();</i>
  50. * </code>
  51. *
  52. *
  53. */
  54. public class GliffyDiagramConverter
  55. {
  56. Logger logger = Logger.getLogger("GliffyDiagramConverter");
  57. private String diagramString;
  58. private Diagram gliffyDiagram;
  59. private mxGraphHeadless drawioDiagram;
  60. private Map<Integer, GliffyObject> vertices;
  61. private Pattern rotationPattern = Pattern.compile("rotation=(\\-?\\w+)");
  62. /**
  63. * Constructs a new converter and starts a conversion.
  64. *
  65. * @param gliffyDiagramString JSON string of a gliffy diagram
  66. */
  67. public GliffyDiagramConverter(String gliffyDiagramString)
  68. {
  69. vertices = new LinkedHashMap<Integer, GliffyObject>();
  70. this.diagramString = gliffyDiagramString;
  71. drawioDiagram = new mxGraphHeadless();
  72. //Disable parent (groups) auto extend feature as it miss with the coordinates of vsdx format
  73. drawioDiagram.setExtendParents(false);
  74. drawioDiagram.setExtendParentsOnAdd(false);
  75. drawioDiagram.setConstrainChildren(false);
  76. start();
  77. }
  78. private void start()
  79. {
  80. // creates a diagram object from the JSON string
  81. this.gliffyDiagram = new GsonBuilder().registerTypeAdapterFactory(new PostDeserializer()).create().fromJson(diagramString, Diagram.class);
  82. collectVerticesAndConvert(vertices, gliffyDiagram.stage.getObjects(), null);
  83. //sort objects by the order specified in the Gliffy diagram
  84. sortObjectsByOrder(gliffyDiagram.stage.getObjects());
  85. drawioDiagram.getModel().beginUpdate();
  86. try
  87. {
  88. for (GliffyObject obj : gliffyDiagram.stage.getObjects())
  89. {
  90. importObject(obj, obj.parent);
  91. }
  92. }
  93. finally
  94. {
  95. drawioDiagram.getModel().endUpdate();
  96. }
  97. }
  98. /**
  99. * Imports the objects into the draw.io diagram. Recursively adds the children
  100. */
  101. private void importObject(GliffyObject obj, GliffyObject gliffyParent)
  102. {
  103. mxCell parent = gliffyParent != null ? gliffyParent.mxObject : null;
  104. drawioDiagram.addCell(obj.mxObject, parent);
  105. if (obj.hasChildren())
  106. {
  107. if (!obj.isSwimlane())
  108. {
  109. // sort the children except for swimlanes
  110. // their order value is "auto"
  111. sortObjectsByOrder(obj.children);
  112. }
  113. for (GliffyObject child : obj.children)
  114. {
  115. importObject(child, obj);
  116. }
  117. }
  118. if (obj.isLine())
  119. {
  120. // gets the terminal cells for the edge
  121. mxCell startTerminal = getTerminalCell(obj, true);
  122. mxCell endTerminal = getTerminalCell(obj, false);
  123. drawioDiagram.addCell(obj.getMxObject(), parent, null,
  124. startTerminal, endTerminal);
  125. setWaypoints(obj, startTerminal, endTerminal);
  126. }
  127. }
  128. private void sortObjectsByOrder(Collection<GliffyObject> values)
  129. {
  130. Comparator<GliffyObject> c = new Comparator<GliffyObject>()
  131. {
  132. public int compare(GliffyObject o1, GliffyObject o2)
  133. {
  134. Float o1o;
  135. Float o2o;
  136. try
  137. {
  138. if (o1.order == null || o2.order == null)
  139. {
  140. return 0;
  141. }
  142. o1o = Float.parseFloat(o1.order);
  143. o2o = Float.parseFloat(o2.order);
  144. return o1o.compareTo(o2o);
  145. }
  146. catch (NumberFormatException e)
  147. {
  148. return o1.order.compareTo(o2.order);
  149. }
  150. }
  151. };
  152. Collections.sort((List<GliffyObject>) values, c);
  153. }
  154. private mxCell getTerminalCell(GliffyObject gliffyEdge, boolean start)
  155. {
  156. Constraints cons = gliffyEdge.getConstraints();
  157. if (cons == null)
  158. {
  159. return null;
  160. }
  161. Constraint con = start ? cons.getStartConstraint()
  162. : cons.getEndConstraint();
  163. if (con == null)
  164. {
  165. return null;
  166. }
  167. ConstraintData cst = start ? con.getStartPositionConstraint()
  168. : con.getEndPositionConstraint();
  169. int nodeId = cst.getNodeId();
  170. GliffyObject gliffyEdgeTerminal = vertices.get(nodeId);
  171. //edge could be terminated with another edge, so import it as a dangling edge
  172. if (gliffyEdgeTerminal == null)
  173. {
  174. return null;
  175. }
  176. mxCell mxEdgeTerminal = gliffyEdgeTerminal.getMxObject();
  177. return mxEdgeTerminal;
  178. }
  179. /**
  180. * Sets the waypoints
  181. *
  182. * @param object Gliffy line
  183. * @param startTerminal starting point
  184. * @param endTerminal ending point
  185. */
  186. private void setWaypoints(GliffyObject object, mxCell startTerminal, mxCell endTerminal)
  187. {
  188. mxCell cell = object.getMxObject();
  189. mxGeometry geo = drawioDiagram.getModel().getGeometry(cell);
  190. geo.setRelative(true);
  191. List<float[]> points = object.getGraphic().getLine().controlPath;
  192. if (points.size() < 2)
  193. {
  194. return;
  195. }
  196. List<mxPoint> mxPoints = new ArrayList<mxPoint>();
  197. mxPoint pivot = new mxPoint(object.x + object.width / 2, object.y + object.height / 2);
  198. for (float[] point : points)
  199. {
  200. mxPoint waypoint = new mxPoint(point[0] + object.x, point[1] + object.y);
  201. if(object.rotation != 0)
  202. {
  203. double rads = Math.toRadians(object.rotation);
  204. double cos = Math.cos(rads);
  205. double sin = Math.sin(rads);
  206. waypoint = mxUtils.getRotatedPoint(waypoint, cos, sin, pivot);
  207. }
  208. mxPoints.add(waypoint);
  209. }
  210. if (startTerminal == null)
  211. {
  212. mxPoint first = mxPoints.get(0);
  213. geo.setTerminalPoint(first, true);
  214. mxPoints.remove(first);// remove first so it doesn't become a waypoint
  215. }
  216. if (endTerminal == null)
  217. {
  218. mxPoint last = mxPoints.get(mxPoints.size() - 1);
  219. geo.setTerminalPoint(last, false);
  220. mxPoints.remove(last);// remove last so it doesn't become a waypoint
  221. }
  222. if (!mxPoints.isEmpty())
  223. {
  224. geo.setPoints(mxPoints);
  225. }
  226. drawioDiagram.getModel().setGeometry(cell, geo);
  227. }
  228. /**
  229. * Creates a map of all vertices so they can be easily accessed when looking
  230. * up terminal cells for edges
  231. */
  232. private void collectVerticesAndConvert(Map<Integer, GliffyObject> vertices,
  233. Collection<GliffyObject> objects, GliffyObject parent)
  234. {
  235. for (GliffyObject object : objects)
  236. {
  237. object.parent = parent;
  238. convertGliffyObject(object, parent);
  239. if (!object.isLine())
  240. {
  241. vertices.put(object.id, object);
  242. }
  243. // don't collect for swimlanes and mindmaps, their children are treated differently
  244. if (object.isGroup() || (object.isLine() && object.hasChildren()))
  245. {
  246. collectVerticesAndConvert(vertices, object.children, object);
  247. }
  248. }
  249. }
  250. /**
  251. * Converts the mxGraph to xml string
  252. *
  253. * @return
  254. * @throws UnsupportedEncodingException
  255. */
  256. public String getGraphXml()
  257. {
  258. mxCodec codec = new mxCodec();
  259. Element node = (Element) codec.encode(drawioDiagram.getModel());
  260. node.setAttribute("style", "default-style2");
  261. node.setAttribute("background",
  262. gliffyDiagram.stage.getBackgroundColor());
  263. node.setAttribute("grid", gliffyDiagram.stage.isGridOn() ? "1" : "0");
  264. node.setAttribute("guides",
  265. gliffyDiagram.stage.isDrawingGuidesOn() ? "1" : "0");
  266. String xml = mxXmlUtils.getXml(node);
  267. return xml;
  268. }
  269. /**
  270. * Performs the object conversion
  271. *
  272. *
  273. */
  274. private mxCell convertGliffyObject(GliffyObject gliffyObject, GliffyObject parent)
  275. {
  276. mxCell cell = new mxCell();
  277. if (gliffyObject.isUnrecognizedGraphicType())
  278. {
  279. logger.warning("Unrecognized graphic type for object with ID : " + gliffyObject.id);
  280. return cell;
  281. }
  282. StringBuilder style = new StringBuilder();
  283. mxGeometry geometry = new mxGeometry(gliffyObject.x, gliffyObject.y, gliffyObject.width, gliffyObject.height);
  284. gliffyObject.adjustGeo(geometry);
  285. cell.setGeometry(geometry);
  286. GliffyObject textObject = null;
  287. String link = null;
  288. Graphic graphic = gliffyObject.getGraphic();
  289. String mxShapeName = StencilTranslator.translate(gliffyObject.uid, graphic != null && graphic.getShape() != null ? graphic.getShape().tid : null);
  290. if (gliffyObject.isGroup())
  291. {
  292. if (graphic == null || mxShapeName == null)
  293. style.append("group;");
  294. cell.setVertex(true);
  295. }
  296. else
  297. {
  298. textObject = gliffyObject.getTextObject();
  299. }
  300. if (graphic != null)
  301. {
  302. link = gliffyObject.getLink();
  303. if (gliffyObject.isShape())
  304. {
  305. GliffyShape shape = graphic.Shape;
  306. cell.setVertex(true);
  307. if (mxShapeName != null)
  308. style.append("shape=").append(mxShapeName).append(";");
  309. if(style.lastIndexOf("shadow=") == -1)
  310. style.append("shadow=" + (shape.dropShadow ? 1 : 0)).append(";");
  311. if(style.lastIndexOf("strokeWidth") == -1)
  312. {
  313. style.append("strokeWidth=" + shape.strokeWidth).append(";");
  314. if (shape.strokeWidth == 0)
  315. style.append("strokeColor=none;");
  316. }
  317. if(style.lastIndexOf("fillColor") == -1)
  318. {
  319. style.append("fillColor=" + shape.fillColor).append(";");
  320. if(shape.fillColor.equals("none"))
  321. style.append("pointerEvents=0;");
  322. }
  323. if(style.lastIndexOf("strokeColor") == -1)
  324. style.append("strokeColor=" + shape.strokeColor).append(";");
  325. if (style.lastIndexOf("gradient") == -1 && shape.gradient && !gliffyObject.isGradientIgnored())
  326. {
  327. style.append("gradientColor=" + gliffyObject.getGradientColor() + ";gradientDirection=north;");
  328. }
  329. // opacity value is wrong for venn circles, so ignore it and use the one in the mapping
  330. if (!gliffyObject.isVennCircle())
  331. {
  332. style.append("opacity=" + shape.opacity * 100).append(";");
  333. style.append(DashStyleMapping.get(shape.dashStyle));
  334. }
  335. style.append(DashStyleMapping.get(shape.dashStyle));
  336. if(gliffyObject.isSubRoutine())
  337. {
  338. //Gliffy's subroutine maps to drawio process, whose inner boundary, unlike subroutine's, is relative to it's width so here we set it to 10px
  339. style.append("size=" + 10 / gliffyObject.width).append(";");
  340. }
  341. }
  342. else if (gliffyObject.isLine())
  343. {
  344. GliffyLine line = graphic.Line;
  345. cell.setEdge(true);
  346. style.append("shape=filledEdge;fixDash=1;");
  347. style.append("strokeWidth=" + line.strokeWidth).append(";");
  348. style.append("strokeColor=" + line.strokeColor).append(";");
  349. style.append("fillColor=" + line.fillColor).append(";");
  350. style.append(ArrowMapping.get(line.startArrow).toString(true)).append(";");
  351. style.append(ArrowMapping.get(line.endArrow).toString(false)).append(";");
  352. style.append(DashStyleMapping.get(line.dashStyle));
  353. style.append(LineMapping.get(line.interpolationType));
  354. geometry.setX(0);
  355. geometry.setY(0);
  356. }
  357. else if (gliffyObject.isText())
  358. {
  359. textObject = gliffyObject;
  360. cell.setVertex(true);
  361. style.append("text;html=1;nl2Br=0;");
  362. cell.setValue(gliffyObject.getText());
  363. //if text is a child of a cell, use relative geometry and set X and Y to 0
  364. if (gliffyObject.parent != null && !gliffyObject.parent.isGroup())
  365. {
  366. mxGeometry parentGeometry = gliffyObject.parent.mxObject.getGeometry();
  367. //if text is a child of a line, special positioning is in place
  368. if(gliffyObject.parent.isLine())
  369. {
  370. /* Gliffy's text offset is a float in the range of [0,1]
  371. * draw.io's text offset is a float in the range of [-1,-1] (while still keeping the text within the line)
  372. * The equation that translates Gliffy offset to draw.io offset is : G*2 - 1 = D
  373. */
  374. mxGeometry mxGeo = new mxGeometry(graphic.Text.lineTValue != null ? graphic.Text.lineTValue * 2 -1 : 0, 0, 0, 0);
  375. mxGeo.setOffset(new mxPoint());
  376. cell.setGeometry(mxGeo);
  377. style.append("labelBackgroundColor=" + gliffyDiagram.stage.getBackgroundColor()).append(";");
  378. //should we force horizontal align for text on lines?
  379. //style.append("align=center;");
  380. }
  381. else
  382. {
  383. cell.setGeometry(new mxGeometry(0, 0, parentGeometry.getWidth(), parentGeometry.getHeight()));
  384. }
  385. cell.getGeometry().setRelative(true);
  386. }
  387. }
  388. else if (gliffyObject.isImage())
  389. {
  390. GliffyImage image = graphic.getImage();
  391. cell.setVertex(true);
  392. style.append("shape=" + StencilTranslator.translate(gliffyObject.uid, null)).append(";");
  393. style.append("image=" + image.getUrl()).append(";");
  394. }
  395. else if (gliffyObject.isSvg())
  396. {
  397. GliffySvg svg = graphic.Svg;
  398. cell.setVertex(true);
  399. style.append("shape=image;aspect=fixed;");
  400. Resource res = gliffyDiagram.embeddedResources.get(svg.embeddedResourceId);
  401. style.append("image=data:image/svg+xml,").append(res.getBase64EncodedData()).append(";");
  402. }
  403. }
  404. // swimlanes have children without uid so their children are converted here ad hoc
  405. else if (gliffyObject.isSwimlane())
  406. {
  407. cell.setVertex(true);
  408. style.append(StencilTranslator.translate(gliffyObject.uid, null)).append(";");
  409. GliffyObject header = gliffyObject.children.get(0);// first child is the header of the swimlane
  410. GliffyShape shape = header.graphic.getShape();
  411. style.append("strokeWidth=" + shape.strokeWidth).append(";");
  412. style.append("shadow=" + (shape.dropShadow ? 1 : 0)).append(";");
  413. style.append("fillColor=" + shape.fillColor).append(";");
  414. style.append("strokeColor=" + shape.strokeColor).append(";");
  415. style.append("startSize=" + header.height).append(";");
  416. style.append("whiteSpace=wrap;");
  417. for (int i = 1; i < gliffyObject.children.size(); i++) // rest of the children are lanes
  418. {
  419. GliffyObject gLane = gliffyObject.children.get(i);
  420. gLane.parent = gliffyObject;
  421. GliffyShape gs = gLane.graphic.getShape();
  422. StringBuilder laneStyle = new StringBuilder();
  423. laneStyle.append("swimlane;swimlaneLine=0;");
  424. laneStyle.append("strokeWidth=" + gs.strokeWidth).append(";");
  425. laneStyle.append("shadow=" + (gs.dropShadow ? 1 : 0)).append(";");
  426. laneStyle.append("fillColor=" + gs.fillColor).append(";");
  427. laneStyle.append("strokeColor=" + gs.strokeColor).append(";");
  428. laneStyle.append("whiteSpace=wrap;html=1;fontStyle=0;");
  429. mxGeometry childGeometry = new mxGeometry(gLane.x, gLane.y, gLane.width, gLane.height);
  430. if(gliffyObject.rotation != 0)
  431. {
  432. laneStyle.append("rotation=" + gliffyObject.rotation).append(";");
  433. Utils.rotatedGeometry(childGeometry, gliffyObject.rotation, gliffyObject.width/ 2, gliffyObject.height / 2);
  434. }
  435. mxCell mxLane = new mxCell();
  436. mxLane.setVertex(true);
  437. cell.insert(mxLane);
  438. GliffyObject laneTxt = gLane.children.get(0);
  439. mxLane.setValue(laneTxt.getText());
  440. laneStyle.append(laneTxt.graphic.getText().getStyle(0, 0));
  441. //for debugging, add gliffy id to the output in the style
  442. laneStyle.append("gliffyId=" + gLane.id + ";");
  443. mxLane.setStyle(laneStyle.toString());
  444. mxLane.setGeometry(childGeometry);
  445. gLane.mxObject = mxLane;
  446. }
  447. }
  448. else if (gliffyObject.isMindmap())
  449. {
  450. GliffyObject rectangle = gliffyObject.children.get(0);
  451. GliffyMindmap mindmap = rectangle.graphic.Mindmap;
  452. style.append("shape=" + StencilTranslator.translate(gliffyObject.uid, null)).append(";");
  453. style.append("shadow=" + (mindmap.dropShadow ? 1 : 0)).append(";");
  454. style.append("strokeWidth=" + mindmap.strokeWidth).append(";");
  455. style.append("fillColor=" + mindmap.fillColor).append(";");
  456. style.append("strokeColor=" + mindmap.strokeColor).append(";");
  457. style.append(DashStyleMapping.get(mindmap.dashStyle));
  458. if (mindmap.gradient)
  459. {
  460. style.append("gradientColor=#FFFFFF;gradientDirection=north;");
  461. }
  462. cell.setVertex(true);
  463. }
  464. if (!gliffyObject.isLine())
  465. {
  466. //if there's a rotation by default, add to it
  467. if(style.lastIndexOf("rotation") != -1)
  468. {
  469. Matcher m = rotationPattern.matcher(style);
  470. if(m.find())
  471. {
  472. String rot = m.group(1);
  473. float initialRotation = Float.parseFloat(rot);
  474. Float rotation = initialRotation + gliffyObject.rotation;
  475. String tmp = m.replaceFirst("rotation=" + rotation.toString());
  476. style.setLength(0);
  477. style.append(tmp);
  478. //handles a specific case where draw.io triangle needs to have an initial rotation of -90 to match that of Gliffy
  479. //in this case, width and height are swapped and x and y are updated
  480. if(style.lastIndexOf("swapwidthandheight") != -1)
  481. {
  482. geometry.setX(geometry.getX() + (geometry.getWidth() - geometry.getHeight()) / 2);
  483. geometry.setY(geometry.getY() + + (geometry.getHeight() - geometry.getWidth()) / 2);
  484. double w = geometry.getWidth();
  485. double h = geometry.getHeight();
  486. geometry.setWidth(h);
  487. geometry.setHeight(w);
  488. }
  489. }
  490. }
  491. else if(gliffyObject.rotation != 0)
  492. {
  493. style.append("rotation=" + gliffyObject.rotation + ";");
  494. }
  495. }
  496. if (textObject != null)
  497. {
  498. style.append("html=1;nl2Br=0;");
  499. if(!gliffyObject.isLine())
  500. {
  501. GliffyText txt = textObject.graphic.getText();
  502. if (gliffyObject.isSwimlane())
  503. {
  504. txt.setForceTopPaddingShift(true);
  505. txt.setValign("middle");
  506. }
  507. cell.setValue(textObject.getText());
  508. gliffyObject.adjustTextPos(textObject);
  509. style.append(textObject == gliffyObject ? txt.getStyle(0, 0) : txt.getStyle(textObject.x, textObject.y));
  510. }
  511. }
  512. if (link != null)
  513. {
  514. Document doc = mxDomUtils.createDocument();
  515. Element uo = doc.createElement("UserObject");
  516. uo.setAttribute("link", link);
  517. drawioDiagram.getModel().setValue(cell, uo);
  518. if(textObject != null)
  519. {
  520. uo.setAttribute("label", textObject.getText());
  521. }
  522. }
  523. //for debugging, add gliffy id to the output in the style
  524. style.append("gliffyId=" + gliffyObject.id + ";");
  525. cell.setStyle(style.toString());
  526. gliffyObject.mxObject = cell;
  527. return cell;
  528. }
  529. }