package com.mxgraph.io; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.mxgraph.io.vdx.PageShapeIDKey; import com.mxgraph.io.vdx.mxMastersManager; import com.mxgraph.io.vdx.mxPropertiesManager; import com.mxgraph.io.vdx.mxStyleSheetManager; import com.mxgraph.io.vdx.mxVdxConstants; import com.mxgraph.io.vdx.mxVdxShape; import com.mxgraph.io.vdx.mxVdxUtils; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxGeometry; import com.mxgraph.util.mxPoint; import com.mxgraph.view.mxCellState; import com.mxgraph.view.mxConnectionConstraint; import com.mxgraph.view.mxGraph; /** * Parses a .vdx XML diagram file and imports it in the given graph.<br/> * This class depends from the classes contained in * com.mxgraph.io.vdx. */ public class mxVdxCodec { /** * Stores the vertexes imported. */ private static HashMap<PageShapeIDKey, mxCell> vertexMap = new HashMap<PageShapeIDKey, mxCell>(); /** * Stores the shapes that represent Edges. */ private static HashMap<PageShapeIDKey, mxVdxShape> edgeShapeMap = new HashMap<PageShapeIDKey, mxVdxShape>(); /** * Stores the shapes that represent Vertexes. */ private static HashMap<PageShapeIDKey, mxVdxShape> vertexShapeMap = new HashMap<PageShapeIDKey, mxVdxShape>(); /** * Stores the parents of the shapes imported. */ private static HashMap<PageShapeIDKey, Object> parentsMap = new HashMap<PageShapeIDKey, Object>(); /** * Stores the page Elements that represents a background. */ private static HashMap<String, Element> backgroundsMap = new HashMap<String, Element>(); private static HashMap<PageShapeIDKey, Object> cellsMap = new HashMap<PageShapeIDKey, Object>(); private static ArrayList<PageShapeIDKey> shapeIDList = new ArrayList<PageShapeIDKey>(); /** * Page Height used in the importPage method.<br/> * This value is accumulated to represents multiple pages. */ private static double pageHeight = 0; /** * Height of the actual page. */ private static double actualPageHeight = 0; /** * Number of pages imported until now.<br/> * This number is used in the keys of the maps. */ private static int pageNumber = 0; /** * Its value determines if text label must be formated with html tags or not.<br/> * Don't confuse with the htmlLabels property of the graph. This property is set * in true at beginning of the decode method. */ private static boolean htmlLabelsEnable = true; /** * Checks if html labels are active. * @return Returns <code>true</code> if html labels are enable. */ public static boolean isHtmlLabelsEnable() { return htmlLabelsEnable; } /** * Sets html labels. * @param htmlLabelsEnable New value of the property. */ public static void setHtmlLabelsEnable(boolean htmlLabelsEnable) { mxVdxCodec.htmlLabelsEnable = htmlLabelsEnable; } /** * Remove all the elements from the defined maps. */ private static void cleanMaps() { vertexMap.clear(); edgeShapeMap.clear(); vertexShapeMap.clear(); parentsMap.clear(); backgroundsMap.clear(); pageHeight = 0; actualPageHeight = 0; } /** * Return the width and height of a Page expressed like a mxPoint. * x = width * y = height * @param page Element that represents a page * @return mxPoint that represents the dimentions of the shape */ private static mxPoint getPageDimentions(Element page) { Element pHeight = (Element) page.getElementsByTagName( mxVdxConstants.PAGE_HEIGHT).item(0); double pageH = Double.valueOf(pHeight.getTextContent()) * mxVdxUtils.conversionFactor(); Element pageWidth = (Element) page.getElementsByTagName( mxVdxConstants.PAGE_WIDTH).item(0); double pageW = Double.valueOf(pageWidth.getTextContent()) * mxVdxUtils.conversionFactor(); return new mxPoint(pageW, pageH); } /** * Calculate the absolute coordinates of a cell's point. * @param cellParent Cell that contains the point. * @param graph Graph where the parsed graph is included. * @param point Point to wich coordinates are calculated. * @return The point in absolute coordinates. */ private static mxPoint calculateAbsolutePoint(mxCell cellParent, mxGraph graph, mxPoint point) { if (cellParent != null) { mxCellState state = graph.getView().getState(cellParent); if (state != null) { point.setX(point.getX() + state.getX()); point.setY(point.getY() + state.getY()); } } return point; } /** * Adds a vertex to the graph if 'shape' is a vertex or add the shape to edgeShapeMap if is a edge. * This method doesn't import the subshapes of 'shape'. * @param graph Graph where the parsed graph is included. * @param parent Parent cell of the shape to be imported. * @param shp Shape to be imported. * @param parentHeight Height of the parent cell. * @return the new vertex added. null if 'shape' is not a vertex. */ private static mxCell addShape(mxGraph graph, Object parent, Element shp, double parentHeight) { //Create a wrapper for shape Element. mxVdxShape shape = new mxVdxShape(shp); //If is a Shape or a Group add the vertex to the graph. if (shp.getAttribute(mxVdxConstants.TYPE).equals( mxVdxConstants.TYPE_SHAPE) || shp.getAttribute(mxVdxConstants.TYPE).equals( mxVdxConstants.TYPE_GROUP)) { String id = shape.getId(); shapeIDList.add(new PageShapeIDKey(pageNumber, id)); //If is a vertex shape if (shape.isVertexShape()) { mxCell v1 = null; if (shape.isComplexShape()) { v1 = shape.addComplexShapeToGraph(graph, parent, parentHeight); } else { v1 = shape.addSimpleShapeToGraph(graph, parent, parentHeight); } vertexMap.put(new PageShapeIDKey(pageNumber, id), v1); vertexShapeMap.put(new PageShapeIDKey(pageNumber, id), shape); cellsMap.put(new PageShapeIDKey(pageNumber, id), v1); return v1; } else { edgeShapeMap.put(new PageShapeIDKey(pageNumber, id), shape); } } return null; } /** * Adds a conected edge to the graph.<br/> * These edge are the referenced in one Connect element at least.<br/> * The edge shape imported is taken from edgeShapeMap and is removed from it. * @param graph graph Graph where the parsed graph is included. * @param parent Parent cell of the edge to be imported. * @param connect Connect Element that references an edge shape and the source vertex. * @param sigConnect Connect Element that references the same edge shape that 'connect' * and the target vertex. This parameter may to be null. * @return The new edge. null if not edge is added. */ private static Object addConectedEdge(mxGraph graph, Element connect, Element sigConnect) { mxCell edge = null; //Retrieve edge Shape and Parent String shapeConnect = connect.getAttribute(mxVdxConstants.FROM_SHEET); mxVdxShape edgeShape = edgeShapeMap.get(new PageShapeIDKey(pageNumber, shapeConnect)); edgeShapeMap.remove(new PageShapeIDKey(pageNumber, shapeConnect)); if (edgeShape != null) { Object parent = parentsMap.get(new PageShapeIDKey(pageNumber, edgeShape.getId())); //Get Parent Height double parentHeight = pageHeight; mxCell parentCell = (mxCell) parent; if (parentCell != null) { mxGeometry parentGeometry = parentCell.getGeometry(); if (parentGeometry != null) { parentHeight = parentGeometry.getHeight(); parentHeight += pageHeight - actualPageHeight; } } //Get beginXY and endXY coordinates. mxPoint beginXY = edgeShape.getBeginXY(parentHeight); beginXY = calculateAbsolutePoint((mxCell) parent, graph, beginXY); mxPoint endXY = edgeShape.getEndXY(parentHeight); endXY = calculateAbsolutePoint((mxCell) parent, graph, endXY); //Declare variables. mxCell source = null; mxCell target = null; mxPoint fromConstraint = null; mxPoint toConstraint = null; //Defines text label String textLabel = edgeShape.getTextLabel(); String from = connect.getAttribute(mxVdxConstants.TO_SHEET); mxVdxShape fromShape = vertexShapeMap.get(new PageShapeIDKey( pageNumber, from)); //If the source is not defined. if (connect.getAttribute(mxVdxConstants.FROM_CELL).equals( mxVdxConstants.END_X) || fromShape == null) { //Only one side connected. source = (mxCell) graph.insertVertex(parent, null, null, beginXY.getX(), beginXY.getY(), 0, 0); fromConstraint = new mxPoint(0, 0); sigConnect = connect; } else { //Define Source vertex of the edge. source = vertexMap.get(new PageShapeIDKey(pageNumber, from)); //Get dimentions of vertex mxPoint dimentionFrom = fromShape.getDimentions(); //Get From shape origin and begin/end of edge in absolutes values. double height = pageHeight; if ((source.getParent() != null) && (source.getParent().getGeometry() != null)) { height = source.getParent().getGeometry().getHeight(); height += pageHeight - actualPageHeight; } mxPoint originFrom = fromShape.getOriginPoint(height); mxPoint absOriginFrom = calculateAbsolutePoint( (mxCell) source.getParent(), graph, originFrom); //Determines From Constraints (Connection point) of the edge. fromConstraint = new mxPoint( (beginXY.getX() - absOriginFrom.getX()) / dimentionFrom.getX(), (beginXY.getY() - absOriginFrom.getY()) / dimentionFrom.getY()); } //If is connected in both sides. if (sigConnect != null) { String to = sigConnect.getAttribute(mxVdxConstants.TO_SHEET); mxVdxShape toShape = vertexShapeMap.get(new PageShapeIDKey( pageNumber, to)); if (toShape != null) { target = vertexMap.get(new PageShapeIDKey(pageNumber, to)); mxPoint dimentionTo = toShape.getDimentions(); //Get To shape origin. double height = pageHeight; if ((target.getParent() != null) && (target.getParent().getGeometry() != null)) { height = target.getParent().getGeometry().getHeight(); height += pageHeight - actualPageHeight; } mxPoint originTo = toShape.getOriginPoint(height); mxPoint absOriginTo = calculateAbsolutePoint( (mxCell) target.getParent(), graph, originTo); //Determines To Constraints (Connection point) of the edge. toConstraint = new mxPoint( (endXY.getX() - absOriginTo.getX()) / dimentionTo.getX(), (endXY.getY() - absOriginTo.getY()) / dimentionTo.getY()); } else { //Only one side connected. target = (mxCell) graph.insertVertex(parent, null, null, endXY.getX(), endXY.getY(), 0, 0); toConstraint = new mxPoint(0, 0); } } else { //Only one side connected. target = (mxCell) graph.insertVertex(parent, null, null, endXY.getX(), endXY.getY(), 0, 0); toConstraint = new mxPoint(0, 0); } //Adjust the constraints. fromConstraint = mxVdxUtils.adjustConstraint(fromConstraint); toConstraint = mxVdxUtils.adjustConstraint(toConstraint); //Defines the style of the edge. String style = edgeShape.getStyleFromEdgeShape(parentHeight); //Insert new edge and set constraints. edge = (mxCell) graph.insertEdge(parent, null, textLabel, source, target, style); graph.setConnectionConstraint(edge, source, true, new mxConnectionConstraint(fromConstraint, false)); graph.setConnectionConstraint(edge, target, false, new mxConnectionConstraint(toConstraint, false)); //Gets and sets routing points of the edge. mxGeometry edgeGeometry = edge.getGeometry(); List<mxPoint> pointList = edgeShape.getRoutingPoints(parentHeight); edgeGeometry.setPoints(pointList); //Put cell in the map. cellsMap.put(new PageShapeIDKey(pageNumber, shapeConnect), edge); } return edge; } /** * Adds a new edge not conected to any vertex to the graph. * @param graph Graph where the parsed graph is included. * @param parent Parent cell of the edge to be imported. * @param edgeShape Shape Element that represents an edge. * @return The new edge added. */ private static Object addNotConnectedEdge(mxGraph graph, Object parent, mxVdxShape edgeShape) { mxCell edge = null; //Defines the label of the edge. String textLabel = edgeShape.getTextLabel(); //Get begin and end of edge. double height = pageHeight; mxCell parentCell = (mxCell) parent; if (parentCell != null) { mxGeometry parentGeometry = parentCell.getGeometry(); if (parentGeometry != null) { height = parentGeometry.getHeight(); } } mxPoint beginXY = edgeShape.getBeginXY(height); mxPoint endXY = edgeShape.getEndXY(height); //Create the source and target cell of the edge. mxCell target = (mxCell) graph.insertVertex(parent, null, null, endXY.getX(), endXY.getY(), 0, 0); mxCell source = (mxCell) graph.insertVertex(parent, null, null, beginXY.getX(), beginXY.getY(), 0, 0); //Define style of the edge String style = edgeShape.getStyleFromEdgeShape(height); //Determines Constraints (Connection points) of the edge. mxPoint fromConstraint = new mxPoint(0, 0); mxPoint toConstraint = new mxPoint(0, 0); //Insert new edge and set constraints. edge = (mxCell) graph.insertEdge(source.getParent(), null, textLabel, source, target, style); graph.setConnectionConstraint(edge, source, true, new mxConnectionConstraint(fromConstraint, false)); graph.setConnectionConstraint(edge, target, false, new mxConnectionConstraint(toConstraint, false)); //Gets and sets routing points of the edge. mxGeometry edgeGeometry = edge.getGeometry(); List<mxPoint> pointList = edgeShape.getRoutingPoints(height); edgeGeometry.setPoints(pointList); cellsMap.put(new PageShapeIDKey(pageNumber, edgeShape.getId()), edge); return edge; } /** * Finds the connect element that corresponds with the connect param. * @param connectList List that contains the connect elements * @param connect Connect Element that references an edge shape. * @param index Index where starts the search. * @return The connect element that corresponds with the connect param. It is, * both references to the same edge shape. */ private static Element findSigConnect(List<Node> connectList, Element connect, int index) { int length = connectList.size(); String shapeConn1 = connect.getAttribute(mxVdxConstants.FROM_SHEET); Element sigConnect = null; boolean end = false; for (int i = index + 1; (i < length) && (!end); i++) { sigConnect = (Element) connectList.get(i); String shapeConn2 = sigConnect .getAttribute(mxVdxConstants.FROM_SHEET); if (shapeConn1.equals(shapeConn2)) { end = true; } else { sigConnect = null; } } return sigConnect; } /** * Adds to the graph all the subshapes included in a shape and recursively. * @param shape Shape element from wich its subshapes will be imported. * @param graph Graph where the parsed graph is included. * @param parent Parent cell of the subShapes to be imported. */ private static void decodeShape(Element shape, mxGraph graph, Object parent) { mxVdxShape shp = new mxVdxShape(shape); //If a shape is complex(formed by several shapes) its subshapes are not considered. //its subshapes have been considered already. if (!shp.isComplexShape()) { NodeList childs = shape.getChildNodes(); if (mxVdxUtils.nodeListHasTag(childs, mxVdxConstants.SHAPES)) { Element shapes = mxVdxUtils.nodeListTag(childs, mxVdxConstants.SHAPES); NodeList shapeList = shapes.getChildNodes(); List<Element> shpList = mxVdxUtils.nodeListTags(shapeList, mxVdxConstants.SHAPE); int shapeLength = shpList.size(); //Get the masterShapes of shape double parentHeight = shp.getDimentions().getY(); //Process the sub-shapes for (int j = 0; j < shapeLength; j++) { Element shapeInside = shpList.get(j); //Get the master of the sub-shape String Id = shapeInside.getAttribute(mxVdxConstants.ID); parentsMap.put(new PageShapeIDKey(pageNumber, Id), parent); Object vertex = addShape(graph, parent, shapeInside, parentHeight); if (vertex != null) { decodeShape(shapeInside, graph, vertex); } } } } } /** * Imports a page of the document with the actual pageHeight.<br/> * In .vdx, the Y-coordinate grows upward from the bottom of the page.<br/> * The page height is used for calculate the correct position in JGraph using * this formula: JGraph_Y_Coord = PageHeight - VDX_Y_Coord. * @param page Actual page Element to be imported * @param graph Graph where the parsed graph is included. * @param parent The parent of the elements to be imported. This should be the default parent. */ private static void importPage(Element page, mxGraph graph, Object parent) { NodeList shapesList = page.getElementsByTagName(mxVdxConstants.SHAPES); //Updates the page number. pageNumber++; if (shapesList.getLength() > 0) { Element shapes = (Element) shapesList.item(0); NodeList shapeList = shapes.getChildNodes(); List<Element> shpList = mxVdxUtils.nodeListTags(shapeList, mxVdxConstants.SHAPE); int shapeLength = shpList.size(); for (int j = 0; j < shapeLength; j++) { Element shape = shpList.get(j); Object vertex = addShape(graph, parent, shape, pageHeight); decodeShape(shape, graph, vertex); } //Process the Connects and add edges. NodeList connectsList = page .getElementsByTagName(mxVdxConstants.CONNECTS); if (connectsList.getLength() > 0) { Element connects = (Element) connectsList.item(0); NodeList connectList = connects .getElementsByTagName(mxVdxConstants.CONNECT); List<Node> list = mxVdxUtils.copyNodeList(connectList); for (int j = 0; j < list.size(); j++) { Element connect = (Element) list.get(j); Element sigConnect = findSigConnect(list, connect, j); list.remove(sigConnect); addConectedEdge(graph, connect, sigConnect); } } //Process not conected edges. Iterator<mxVdxShape> it = edgeShapeMap.values().iterator(); while (it.hasNext()) { mxVdxShape edgeShape = it.next(); addNotConnectedEdge(graph, parentsMap.get(new PageShapeIDKey( pageNumber, edgeShape.getId())), edgeShape); } } } /** * Recieves a xml document and parses it generating a new graph that is inserted in graph. * @param document XML to be parsed * @param graph Graph where the parsed graph is included. */ public static void decode(Document document, mxGraph graph) { Object parent = graph.getDefaultParent(); graph.getModel().beginUpdate(); graph.setHtmlLabels(true); Document doc = document; //Inicialize the Style Sheet Manager mxStyleSheetManager.getInstance().initialise(doc); //Inicialize the Master Manager mxMastersManager.getInstance().initialise(doc); //Inicialize the Properties Manager mxPropertiesManager.getInstance().initialise(doc); //Imports each page of the document. NodeList vdxPages = doc.getElementsByTagName(mxVdxConstants.PAGES); if (vdxPages.getLength() > 0) { Element pages = (Element) vdxPages.item(0); NodeList pageList = pages.getElementsByTagName(mxVdxConstants.PAGE); if (pageList.getLength() > 0) { //Retrieves the backgrounds pages for (int p = 0; p < pageList.getLength(); p++) { Element page = (Element) pageList.item(p); String back = page.getAttribute(mxVdxConstants.BACKGROUND); if ((back != null && back.equals(mxVdxConstants.TRUE))) { String id = page.getAttribute(mxVdxConstants.ID); backgroundsMap.put(id, page); } } //Import the pages that are not background. //If a page references a background page, the background is imported previously //to the actual page. for (int p = 0; p < pageList.getLength(); p++) { Element page = (Element) pageList.item(p); String back = page.getAttribute(mxVdxConstants.BACKGROUND); if (!(back != null && back.equals(mxVdxConstants.TRUE))) { actualPageHeight = getPageDimentions(page).getY(); pageHeight += actualPageHeight; String backId = page .getAttribute(mxVdxConstants.BACK_PAGE); if (backId != null && !backId.equals("")) { //Import the background. Element background = backgroundsMap.get(backId); importPage(background, graph, parent); } //Import the actual page. importPage(page, graph, parent); } } } } Object[] order = mxVdxUtils.getOrderArray(shapeIDList, cellsMap); graph.orderCells(false, order); graph.getModel().endUpdate(); cleanMaps(); } }