/** * $Id: mxUtils.java,v 1.111 2011-04-04 01:56:36 david Exp $ * Copyright (c) 2007-2011, Gaudenz Alder, David Benson */ package com.mxgraph.util; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.font.FontRenderContext; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringReader; import java.io.StringWriter; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Formatter; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.Stack; import java.util.TreeSet; import javax.imageio.ImageIO; import javax.swing.text.html.HTMLDocument; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.xml.sax.InputSource; import com.mxgraph.io.mxCodecRegistry; import com.mxgraph.model.mxCellPath; import com.mxgraph.model.mxICell; import com.mxgraph.model.mxIGraphModel; import com.mxgraph.view.mxCellState; /** * Contains various helper methods for use with mxGraph. */ public class mxUtils { /** * True if the machine is a Mac. */ public static boolean IS_MAC = System.getProperty("os.name").toLowerCase() .indexOf("mac") >= 0; /** * Static Graphics used for Font Metrics. */ protected static transient Graphics fontGraphics; // Creates a renderer for HTML markup (only possible in // non-headless environment) static { try { fontGraphics = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB) .getGraphics(); } catch (Exception e) { // ignore } } /** * Returns the size for the given label. If isHtml is true then any HTML * markup in the label is computed as HTML and all newlines inside the HTML * body are converted into linebreaks. */ public static mxRectangle getLabelSize(String label, Map<String, Object> style, boolean isHtml, double scale) { mxRectangle size; if (isHtml) { size = getSizeForHtml(getBodyMarkup(label, true), style, scale); } else { size = getSizeForString(label, getFont(style), scale); } return size; } /** * Returns the body part of the given HTML markup. */ public static String getBodyMarkup(String markup, boolean replaceLinefeeds) { String lowerCase = markup.toLowerCase(); int bodyStart = lowerCase.indexOf("<body>"); if (bodyStart >= 0) { bodyStart += 7; int bodyEnd = lowerCase.lastIndexOf("</body>"); if (bodyEnd > bodyStart) { markup = markup.substring(bodyStart, bodyEnd).trim(); } } if (replaceLinefeeds) { markup = markup.replaceAll("\n", "<br>"); } return markup; } /** * Returns the paint bounds for the given label. */ public static mxRectangle getLabelPaintBounds(String label, Map<String, Object> style, boolean isHtml, mxPoint offset, mxRectangle vertexBounds, double scale) { mxRectangle size = mxUtils.getLabelSize(label, style, isHtml, scale); // Measures font with full scale and scales back size.setWidth(size.getWidth() / scale); size.setHeight(size.getHeight() / scale); double x = offset.getX(); double y = offset.getY(); double width = 0; double height = 0; if (vertexBounds != null) { x += vertexBounds.getX(); y += vertexBounds.getY(); if (mxUtils.getString(style, mxConstants.STYLE_SHAPE, "").equals( mxConstants.SHAPE_SWIMLANE)) { // Limits the label to the swimlane title boolean horizontal = mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true); double start = mxUtils.getDouble(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE) * scale; if (horizontal) { width += vertexBounds.getWidth(); height += start; } else { width += start; height += vertexBounds.getHeight(); } } else { width += vertexBounds.getWidth(); height += vertexBounds.getHeight(); } } return mxUtils.getScaledLabelBounds(x, y, size, width, height, style, scale); } /** * Returns the bounds for a label for the given location and size, taking * into account the alignment and spacing in the specified style, as well as * the width and height of the rectangle that contains the label. (For edge * labels this width and height is 0.) The scale is used to scale the given * size and the spacings in the specified style. */ public static mxRectangle getScaledLabelBounds(double x, double y, mxRectangle size, double outerWidth, double outerHeight, Map<String, Object> style, double scale) { double inset = mxConstants.LABEL_INSET * scale; // Scales the size of the label // FIXME: Correct rounded font size and not-rounded scale double width = size.getWidth() * scale + 2 * inset; double height = size.getHeight() * scale + 2 * inset; // Gets the global spacing and orientation boolean horizontal = isTrue(style, mxConstants.STYLE_HORIZONTAL, true); int spacing = (int) (getInt(style, mxConstants.STYLE_SPACING) * scale); // Gets the alignment settings Object align = getString(style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER); Object valign = getString(style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE); // Gets the vertical spacing int top = (int) (getInt(style, mxConstants.STYLE_SPACING_TOP) * scale); int bottom = (int) (getInt(style, mxConstants.STYLE_SPACING_BOTTOM) * scale); // Gets the horizontal spacing int left = (int) (getInt(style, mxConstants.STYLE_SPACING_LEFT) * scale); int right = (int) (getInt(style, mxConstants.STYLE_SPACING_RIGHT) * scale); // Applies the orientation to the spacings and dimension if (!horizontal) { int tmp = top; top = right; right = bottom; bottom = left; left = tmp; double tmp2 = width; width = height; height = tmp2; } // Computes the position of the label for the horizontal alignment if ((horizontal && align.equals(mxConstants.ALIGN_CENTER)) || (!horizontal && valign.equals(mxConstants.ALIGN_MIDDLE))) { x += (outerWidth - width) / 2 + left - right; } else if ((horizontal && align.equals(mxConstants.ALIGN_RIGHT)) || (!horizontal && valign.equals(mxConstants.ALIGN_BOTTOM))) { x += outerWidth - width - spacing - right; } else { x += spacing + left; } // Computes the position of the label for the vertical alignment if ((!horizontal && align.equals(mxConstants.ALIGN_CENTER)) || (horizontal && valign.equals(mxConstants.ALIGN_MIDDLE))) { y += (outerHeight - height) / 2 + top - bottom; } else if ((!horizontal && align.equals(mxConstants.ALIGN_LEFT)) || (horizontal && valign.equals(mxConstants.ALIGN_BOTTOM))) { y += outerHeight - height - spacing - bottom; } else { y += spacing + top; } return new mxRectangle(x, y, width, height); } /** * Returns the font metrics of the static font graphics instance * @param font The font whose metrics are to be returned * @return the font metrics of the specified font */ public static FontMetrics getFontMetrics(Font font) { if (fontGraphics != null) { return fontGraphics.getFontMetrics(font); } return null; } /** * Returns an <mxRectangle> with the size (width and height in pixels) of * the given string. * * @param text * String whose size should be returned. * @param font * Font to be used for the computation. */ public static mxRectangle getSizeForString(String text, Font font, double scale) { FontRenderContext frc = new FontRenderContext(null, false, false); font = font.deriveFont((float) (font.getSize2D() * scale)); FontMetrics metrics = null; if (fontGraphics != null) { metrics = fontGraphics.getFontMetrics(font); } double lineHeight = mxConstants.LINESPACING; if (metrics != null) { lineHeight += metrics.getHeight(); } else { lineHeight += font.getSize2D() * 1.27; } String[] lines = text.split("\n"); Rectangle2D boundingBox = null; for (int i = 0; i < lines.length; i++) { Rectangle2D bounds = font.getStringBounds(lines[i], frc); if (boundingBox == null) { boundingBox = bounds; } else { boundingBox.setFrame(0, 0, Math.max(boundingBox.getWidth(), bounds.getWidth()), boundingBox.getHeight() + lineHeight); } } return new mxRectangle(boundingBox); } /** * Returns the specified text in lines that fit within the specified * width when the specified font metrics are applied to the text * @param text the text to wrap * @param metrics the font metrics to calculate the text size for * @param width the width that the text must fit within * @return the input text split in lines that fit the specified width */ public static String[] wordWrap(String text, FontMetrics metrics, double width) { List<String> result = new ArrayList<String>(); // First split the processing into lines already delimited by // newlines. We want the result to retain all newlines in position. String[] lines = text.split("\n"); for (int i = 0; i < lines.length; i++) { int lineWidth = 0; // the display width of the current line int charCount = 0; // keeps count of current position in the line StringBuilder currentLine = new StringBuilder(); // Split the words of the current line by spaces and tabs // The words are trimmed of tabs, space and newlines, therefore String[] words = lines[i].split("\\s+"); // Need to a form a stack of the words in reverse order // This is because if a word is split during the process // the remainder of the word is added to the front of the // stack and processed next Stack<String> wordStack = new Stack<String>(); for (int j = words.length - 1; j >= 0; j--) { wordStack.push(words[j]); } while (!wordStack.isEmpty()) { String word = wordStack.pop(); // Work out what whitespace exists before this word. // and add the width of the whitespace to the calculation int whitespaceCount = 0; if (word.length() > 0) { // Concatenate any preceding whitespace to the // word and calculate the number of characters of that // whitespace char firstWordLetter = word.charAt(0); int letterIndex = lines[i].indexOf(firstWordLetter, charCount); String whitespace = lines[i].substring(charCount, letterIndex); whitespaceCount = whitespace.length(); word = whitespace.concat(word); } double wordLength; // If the line width is zero, we are at the start of a newline // We don't proceed preceeding whitespace in the width // calculation if (lineWidth > 0) { wordLength = metrics.stringWidth(word); } else { wordLength = metrics.stringWidth(word.trim()); } // Does the width of line so far plus the width of the // current word exceed the allowed width? if (lineWidth + wordLength > width) { if (lineWidth > 0) { // There is already at least one word on this line // and the current word takes the overall width over // the allowed width. Because there is something on // the line, complete the current line, reset the width // counter, create a new line and put the current word // back on the stack for processing in the next round result.add(currentLine.toString()); currentLine = new StringBuilder(); wordStack.push(word.trim()); lineWidth = 0; } else { // There are no words on the current line and the // current word does not fit on it. Find the maximum // number of characters of this word that just fit // in the available width word = word.trim(); for (int j = 1; j <= word.length(); j++) { wordLength = metrics.stringWidth(word.substring(0, j)); if (lineWidth + wordLength > width) { // The last character took us over the allowed // width, deducted it unless there is only one // character, in which case we have to use it // since we can't split it... j = j > 1 ? j - 1 : j; String chars = word.substring(0, j); currentLine = currentLine.append(chars); // Return the unprocessed part of the word // to the stack wordStack .push(word.substring(j, word.length())); result.add(currentLine.toString()); currentLine = new StringBuilder(); lineWidth = 0; // Increment char counter allowing for white // space in the original word charCount = charCount + chars.length() + whitespaceCount; break; } } } } else { // The current word does not take the total line width // over the allowed width. Append the word, removing // preceeding whitespace if it is the first word in the // line. if (lineWidth > 0) { currentLine = currentLine.append(word); } else { currentLine = currentLine.append(word.trim()); } lineWidth += wordLength; charCount += word.length(); } } // If there is anything in the current line after processing all of // the words in this line, add it to the result. if (currentLine.length() > 0 || result.isEmpty()) { result.add(currentLine.toString()); } } return result.toArray(new String[result.size()]); } /** * Returns an mxRectangle with the size (width and height in pixels) of the * given HTML markup. * * @param markup * HTML markup whose size should be returned. */ public static mxRectangle getSizeForHtml(String markup, Map<String, Object> style, double scale) { mxLightweightLabel textRenderer = mxLightweightLabel .getSharedInstance(); if (textRenderer != null) { textRenderer.setText(createHtmlDocument(style, markup)); Dimension size = textRenderer.getPreferredSize(); return new mxRectangle(0, 0, size.width, size.height); } else { return getSizeForString(markup, getFont(style), scale); } } /** * Returns the bounding box for the rotated rectangle. */ public static mxRectangle getBoundingBox(mxRectangle rect, double rotation) { mxRectangle result = null; if (rect != null && rotation != 0) { double rad = Math.toRadians(rotation); double cos = Math.cos(rad); double sin = Math.sin(rad); mxPoint cx = new mxPoint(rect.getX() + rect.getWidth() / 2, rect.getY() + rect.getHeight() / 2); mxPoint p1 = new mxPoint(rect.getX(), rect.getY()); mxPoint p2 = new mxPoint(rect.getX() + rect.getWidth(), rect.getY()); mxPoint p3 = new mxPoint(p2.getX(), rect.getY() + rect.getHeight()); mxPoint p4 = new mxPoint(rect.getX(), p3.getY()); p1 = getRotatedPoint(p1, cos, sin, cx); p2 = getRotatedPoint(p2, cos, sin, cx); p3 = getRotatedPoint(p3, cos, sin, cx); p4 = getRotatedPoint(p4, cos, sin, cx); Rectangle tmp = new Rectangle((int) p1.getX(), (int) p1.getY(), 0, 0); tmp.add(p2.getPoint()); tmp.add(p3.getPoint()); tmp.add(p4.getPoint()); result = new mxRectangle(tmp); } else if (rect != null) { result = (mxRectangle) rect.clone(); } return result; } /** * Find the first character matching the input character in the given * string where the character has no letter preceding it. * * @param text the string to test for the presence of the input character * @param inputChar the test character * @param fromIndex the index position of the string to start from * @return */ public static int firstCharAt(String text, int inputChar, int fromIndex) { int result = 0; while (result >= 0) { result = text.indexOf(inputChar, fromIndex); if (result == 0) { return result; } else if (result > 0) { // Check there is a whitespace or symbol before the hit character if (Character.isLetter(text.codePointAt(result - 1))) { // The pre-increment is used in if and else branches. if (++fromIndex >= text.length()) { return -1; } else { // Test again from next candidate character // This isn't the first letter of this word result = text.indexOf(inputChar, fromIndex); } } else { return result; } } } return result; } /** * Rotates the given point by the given cos and sin. */ public static mxPoint getRotatedPoint(mxPoint pt, double cos, double sin) { return getRotatedPoint(pt, cos, sin, new mxPoint()); } /** * Finds the index of the nearest segment on the given cell state for the * specified coordinate pair. */ public static int findNearestSegment(mxCellState state, double x, double y) { int index = -1; if (state.getAbsolutePointCount() > 0) { mxPoint last = state.getAbsolutePoint(0); double min = Double.MAX_VALUE; for (int i = 1; i < state.getAbsolutePointCount(); i++) { mxPoint current = state.getAbsolutePoint(i); double dist = new Line2D.Double(last.x, last.y, current.x, current.y).ptSegDistSq(x, y); if (dist < min) { min = dist; index = i - 1; } last = current; } } return index; } /** * Rotates the given point by the given cos and sin. */ public static mxPoint getRotatedPoint(mxPoint pt, double cos, double sin, mxPoint c) { double x = pt.getX() - c.getX(); double y = pt.getY() - c.getY(); double x1 = x * cos - y * sin; double y1 = y * cos + x * sin; return new mxPoint(x1 + c.getX(), y1 + c.getY()); } /** * Returns an integer mask of the port constraints of the given map * @param dict the style map to determine the port constraints for * @return the mask of port constraint directions */ public static int getPortConstraints(Map<String, Object> dict) { return getPortConstraints(dict, mxConstants.DIRECTION_MASK_ALL); } /** * Returns an integer mask of the port constraints of the given map * @param dict the style map to determine the port constraints for * @param defaultValue Default value to return if the key is undefined. * @return the mask of port constraint directions */ public static int getPortConstraints(Map<String, Object> dict, int defaultValue) { Object value = dict.get(mxConstants.STYLE_PORT_CONSTRAINT); if (value == null) { return defaultValue; } else { String directions = value.toString(); int returnValue = mxConstants.DIRECTION_MASK_NONE; if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0) { returnValue |= mxConstants.DIRECTION_MASK_NORTH; } if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0) { returnValue |= mxConstants.DIRECTION_MASK_WEST; } if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0) { returnValue |= mxConstants.DIRECTION_MASK_SOUTH; } if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0) { returnValue |= mxConstants.DIRECTION_MASK_EAST; } return returnValue; } } public static int reversePortConstraints(int constraint) { int result = 0; result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3; result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1; result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1; result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3; return result; } /** * Draws the image inside the clip bounds to the given graphics object. */ public static void drawImageClip(Graphics g, BufferedImage image, ImageObserver observer) { Rectangle clip = g.getClipBounds(); if (clip != null) { int w = image.getWidth(); int h = image.getHeight(); int x = Math.max(0, Math.min(clip.x, w)); int y = Math.max(0, Math.min(clip.y, h)); w = Math.min(clip.width, w - x); h = Math.min(clip.height, h - y); if (w > 0 && h > 0) { // TODO: Support for normal images using fast subimage copies g.drawImage(image.getSubimage(x, y, w, h), clip.x, clip.y, observer); } } else { g.drawImage(image, 0, 0, observer); } } /** * */ public static void fillClippedRect(Graphics g, int x, int y, int width, int height) { Rectangle bg = new Rectangle(x, y, width, height); try { if (g.getClipBounds() != null) { bg = bg.intersection(g.getClipBounds()); } } catch (Exception e) { // FIXME: Getting clipbounds sometimes throws an NPE } g.fillRect(bg.x, bg.y, bg.width, bg.height); } /** * Creates a new list of new points obtained by translating the points in * the given list by the given vector. Elements that are not mxPoints are * added to the result as-is. */ public static List<mxPoint> translatePoints(List<mxPoint> pts, double dx, double dy) { List<mxPoint> result = null; if (pts != null) { result = new ArrayList<mxPoint>(pts.size()); Iterator<mxPoint> it = pts.iterator(); while (it.hasNext()) { mxPoint point = (mxPoint) it.next().clone(); point.setX(point.getX() + dx); point.setY(point.getY() + dy); result.add(point); } } return result; } /** * Returns the intersection of two lines as an mxPoint. * * @param x0 * X-coordinate of the first line's startpoint. * @param y0 * Y-coordinate of the first line's startpoint. * @param x1 * X-coordinate of the first line's endpoint. * @param y1 * Y-coordinate of the first line's endpoint. * @param x2 * X-coordinate of the second line's startpoint. * @param y2 * Y-coordinate of the second line's startpoint. * @param x3 * X-coordinate of the second line's endpoint. * @param y3 * Y-coordinate of the second line's endpoint. * @return Returns the intersection between the two lines. */ public static mxPoint intersection(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) { double denom = ((y3 - y2) * (x1 - x0)) - ((x3 - x2) * (y1 - y0)); double nume_a = ((x3 - x2) * (y0 - y2)) - ((y3 - y2) * (x0 - x2)); double nume_b = ((x1 - x0) * (y0 - y2)) - ((y1 - y0) * (x0 - x2)); double ua = nume_a / denom; double ub = nume_b / denom; if (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) { // Get the intersection point double intersectionX = x0 + ua * (x1 - x0); double intersectionY = y0 + ua * (y1 - y0); return new mxPoint(intersectionX, intersectionY); } // No intersection return null; } /** * Sorts the given cells according to the order in the cell hierarchy. */ public static Object[] sortCells(Object[] cells, final boolean ascending) { return sortCells(Arrays.asList(cells), ascending).toArray(); } /** * Sorts the given cells according to the order in the cell hierarchy. */ public static Collection<Object> sortCells(Collection<Object> cells, final boolean ascending) { SortedSet<Object> result = new TreeSet<Object>(new Comparator<Object>() { public int compare(Object o1, Object o2) { int comp = mxCellPath.compare(mxCellPath.create((mxICell) o1), mxCellPath.create((mxICell) o2)); return (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1); } }); result.addAll(cells); return result; } /** * Returns true if the given array contains the given object. */ public static boolean contains(Object[] array, Object obj) { return indexOf(array, obj) >= 0; } /** * Returns the index of the given object in the given array of -1 if the * object is not contained in the array. */ public static int indexOf(Object[] array, Object obj) { if (obj != null && array != null) { for (int i = 0; i < array.length; i++) { if (array[i] == obj) { return i; } } } return -1; } /** * Returns the stylename in a style of the form stylename[;key=value] or an * empty string if the given style does not contain a stylename. * * @param style * String of the form stylename[;key=value]. * @return Returns the stylename from the given formatted string. */ public static String getStylename(String style) { if (style != null) { String[] pairs = style.split(";"); String stylename = pairs[0]; if (stylename.indexOf("=") < 0) { return stylename; } } return ""; } /** * Returns the stylenames in a style of the form stylename[;key=value] or an * empty array if the given style does not contain any stylenames. * * @param style * String of the form stylename[;stylename][;key=value]. * @return Returns the stylename from the given formatted string. */ public static String[] getStylenames(String style) { List<String> result = new ArrayList<String>(); if (style != null) { String[] pairs = style.split(";"); for (int i = 0; i < pairs.length; i++) { if (pairs[i].indexOf("=") < 0) { result.add(pairs[i]); } } } return result.toArray(new String[result.size()]); } /** * Returns the index of the given stylename in the given style. This returns * -1 if the given stylename does not occur (as a stylename) in the given * style, otherwise it returns the index of the first character. */ public static int indexOfStylename(String style, String stylename) { if (style != null && stylename != null) { String[] tokens = style.split(";"); int pos = 0; for (int i = 0; i < tokens.length; i++) { if (tokens[i].equals(stylename)) { return pos; } pos += tokens[i].length() + 1; } } return -1; } /** * Adds the specified stylename to the given style if it does not already * contain the stylename. */ public String addStylename(String style, String stylename) { if (indexOfStylename(style, stylename) < 0) { if (style == null) { style = ""; } else if (style.length() > 0 && style.charAt(style.length() - 1) != ';') { style += ';'; } style += stylename; } return style; } /** * Removes all occurrences of the specified stylename in the given style and * returns the updated style. Trailing semicolons are preserved. */ public String removeStylename(String style, String stylename) { StringBuffer buffer = new StringBuffer(); if (style != null) { String[] tokens = style.split(";"); for (int i = 0; i < tokens.length; i++) { if (!tokens[i].equals(stylename)) { buffer.append(tokens[i] + ";"); } } } return (buffer.length() > 1) ? buffer.substring(0, buffer.length() - 1) : buffer.toString(); } /** * Removes all stylenames from the given style and returns the updated * style. */ public static String removeAllStylenames(String style) { StringBuffer buffer = new StringBuffer(); if (style != null) { String[] tokens = style.split(";"); for (int i = 0; i < tokens.length; i++) { if (tokens[i].indexOf('=') >= 0) { buffer.append(tokens[i] + ";"); } } } return (buffer.length() > 1) ? buffer.substring(0, buffer.length() - 1) : buffer.toString(); } /** * Assigns the value for the given key in the styles of the given cells, or * removes the key from the styles if the value is null. * * @param model * Model to execute the transaction in. * @param cells * Array of cells to be updated. * @param key * Key of the style to be changed. * @param value * New value for the given key. */ public static void setCellStyles(mxIGraphModel model, Object[] cells, String key, String value) { if (cells != null && cells.length > 0) { model.beginUpdate(); try { for (int i = 0; i < cells.length; i++) { if (cells[i] != null) { String style = setStyle(model.getStyle(cells[i]), key, value); model.setStyle(cells[i], style); } } } finally { model.endUpdate(); } } } /** * Adds or removes the given key, value pair to the style and returns the * new style. If value is null or zero length then the key is removed from * the style. * * @param style * String of the form <code>stylename[;key=value]</code>. * @param key * Key of the style to be changed. * @param value * New value for the given key. * @return Returns the new style. */ public static String setStyle(String style, String key, String value) { boolean isValue = value != null && value.length() > 0; if (style == null || style.length() == 0) { if (isValue) { style = key + "=" + value; } } else { int index = style.indexOf(key + "="); if (index < 0) { if (isValue) { String sep = (style.endsWith(";")) ? "" : ";"; style = style + sep + key + '=' + value; } } else { String tmp = (isValue) ? key + "=" + value : ""; int cont = style.indexOf(";", index); if (!isValue) { cont++; } style = style.substring(0, index) + tmp + ((cont > index) ? style.substring(cont) : ""); } } return style; } /** * Sets or toggles the flag bit for the given key in the cell's styles. If * value is null then the flag is toggled. * * <code> * mxUtils.setCellStyleFlags(graph.getModel(), * cells, * mxConstants.STYLE_FONTSTYLE, * mxConstants.FONT_BOLD, null); * </code> * * Toggles the bold font style. * * @param model * Model that contains the cells. * @param cells * Array of cells to change the style for. * @param key * Key of the style to be changed. * @param flag * Integer for the bit to be changed. * @param value * Optional boolean value for the flag. */ public static void setCellStyleFlags(mxIGraphModel model, Object[] cells, String key, int flag, Boolean value) { if (cells != null && cells.length > 0) { model.beginUpdate(); try { for (int i = 0; i < cells.length; i++) { if (cells[i] != null) { String style = setStyleFlag(model.getStyle(cells[i]), key, flag, value); model.setStyle(cells[i], style); } } } finally { model.endUpdate(); } } } /** * Sets or removes the given key from the specified style and returns the * new style. If value is null then the flag is toggled. * * @param style * String of the form stylename[;key=value]. * @param key * Key of the style to be changed. * @param flag * Integer for the bit to be changed. * @param value * Optional boolean value for the given flag. */ public static String setStyleFlag(String style, String key, int flag, Boolean value) { if (style == null || style.length() == 0) { if (value == null || value.booleanValue()) { style = key + "=" + flag; } else { style = key + "=0"; } } else { int index = style.indexOf(key + "="); if (index < 0) { String sep = (style.endsWith(";")) ? "" : ";"; if (value == null || value.booleanValue()) { style = style + sep + key + "=" + flag; } else { style = style + sep + key + "=0"; } } else { int cont = style.indexOf(";", index); String tmp = ""; int result = 0; if (cont < 0) { tmp = style.substring(index + key.length() + 1); } else { tmp = style.substring(index + key.length() + 1, cont); } if (value == null) { result = Integer.parseInt(tmp) ^ flag; } else if (value.booleanValue()) { result = Integer.parseInt(tmp) | flag; } else { result = Integer.parseInt(tmp) & ~flag; } style = style.substring(0, index) + key + "=" + result + ((cont >= 0) ? style.substring(cont) : ""); } } return style; } public static boolean intersectsHotspot(mxCellState state, int x, int y, double hotspot) { return intersectsHotspot(state, x, y, hotspot, 0, 0); } /** * Returns true if the given coordinate pair intersects the hotspot of the * given state. */ public static boolean intersectsHotspot(mxCellState state, int x, int y, double hotspot, int min, int max) { if (hotspot > 0) { int cx = (int) Math.round(state.getCenterX()); int cy = (int) Math.round(state.getCenterY()); int width = (int) Math.round(state.getWidth()); int height = (int) Math.round(state.getHeight()); if (mxUtils .getString(state.getStyle(), mxConstants.STYLE_SHAPE, "") .equals(mxConstants.SHAPE_SWIMLANE)) { int start = mxUtils.getInt(state.getStyle(), mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE); if (mxUtils.isTrue(state.getStyle(), mxConstants.STYLE_HORIZONTAL, true)) { cy = (int) Math.round(state.getY() + start / 2); height = start; } else { cx = (int) Math.round(state.getX() + start / 2); width = start; } } int w = (int) Math.max(min, width * hotspot); int h = (int) Math.max(min, height * hotspot); if (max > 0) { w = Math.min(w, max); h = Math.min(h, max); } Rectangle rect = new Rectangle(Math.round(cx - w / 2), Math.round(cy - h / 2), w, h); return rect.contains(x, y); } return true; } /** * Returns true if the dictionary contains true for the given key or false * if no value is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @return Returns the boolean value for key in dict. */ public static boolean isTrue(Map<String, Object> dict, String key) { return isTrue(dict, key, false); } /** * Returns true if the dictionary contains true for the given key or the * given default value if no value is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @param defaultValue * Default value to return if the key is undefined. * @return Returns the boolean value for key in dict. */ public static boolean isTrue(Map<String, Object> dict, String key, boolean defaultValue) { Object value = dict.get(key); if (value == null) { return defaultValue; } else { return value.equals("1") || value.toString().toLowerCase().equals("true"); } } /** * Returns the value for key in dictionary as an int or 0 if no value is * defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @return Returns the integer value for key in dict. */ public static int getInt(Map<String, Object> dict, String key) { return getInt(dict, key, 0); } /** * Returns the value for key in dictionary as an int or the given default * value if no value is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @param defaultValue * Default value to return if the key is undefined. * @return Returns the integer value for key in dict. */ public static int getInt(Map<String, Object> dict, String key, int defaultValue) { Object value = dict.get(key); if (value == null) { return defaultValue; } else { // Handles commas by casting them to an int return (int) Float.parseFloat(value.toString()); } } /** * Returns the value for key in dictionary as a float or 0 if no value is * defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @return Returns the float value for key in dict. */ public static float getFloat(Map<String, Object> dict, String key) { return getFloat(dict, key, 0); } /** * Returns the value for key in dictionary as a float or the given default * value if no value is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @param defaultValue * Default value to return if the key is undefined. * @return Returns the float value for key in dict. */ public static float getFloat(Map<String, Object> dict, String key, float defaultValue) { Object value = dict.get(key); if (value == null) { return defaultValue; } else { return Float.parseFloat(value.toString()); } } /** * Returns the value for key in dictionary as a float array or the given default * value if no value is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @param defaultValue * Default value to return if the key is undefined. * @return Returns the float array value for key in dict. */ public static float[] getFloatArray(Map<String, Object> dict, String key, float[] defaultValue) { Object value = dict.get(key); if (value == null) { return defaultValue; } else { String[] floatChars = value.toString().split(","); float[] result = new float[floatChars.length]; for (int i = 0; i < floatChars.length; i++) { result[i] = Float.parseFloat(floatChars[i]); } return result; } } /** * Returns the value for key in dictionary as a double or 0 if no value is * defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @return Returns the double value for key in dict. */ public static double getDouble(Map<String, Object> dict, String key) { return getDouble(dict, key, 0); } /** * Returns the value for key in dictionary as a double or the given default * value if no value is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @param defaultValue * Default value to return if the key is undefined. * @return Returns the double value for key in dict. */ public static double getDouble(Map<String, Object> dict, String key, double defaultValue) { Object value = dict.get(key); if (value == null) { return defaultValue; } else { return Double.parseDouble(value.toString()); } } /** * Returns the value for key in dictionary as a string or null if no value * is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @return Returns the string value for key in dict. */ public static String getString(Map<String, Object> dict, String key) { return getString(dict, key, null); } /** * Returns the value for key in dictionary as a string or the given default * value if no value is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @param defaultValue * Default value to return if the key is undefined. * @return Returns the string value for key in dict. */ public static String getString(Map<String, Object> dict, String key, String defaultValue) { Object value = dict.get(key); if (value == null) { return defaultValue; } else { return value.toString(); } } /** * Returns the value for key in dictionary as a color or null if no value is * defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @return Returns the color value for key in dict. */ public static Color getColor(Map<String, Object> dict, String key) { return getColor(dict, key, null); } /** * Returns the value for key in dictionary as a color or the given default * value if no value is defined for the key. * * @param dict * Dictionary that contains the key, value pairs. * @param key * Key whose value should be returned. * @param defaultValue * Default value to return if the key is undefined. * @return Returns the color value for key in dict. */ public static Color getColor(Map<String, Object> dict, String key, Color defaultValue) { Object value = dict.get(key); if (value == null) { return defaultValue; } else { return parseColor(value.toString()); } } /** * */ public static Font getFont(Map<String, Object> style) { return getFont(style, 1); } /** * */ public static Font getFont(Map<String, Object> style, double scale) { String fontFamily = getString(style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY); int fontSize = getInt(style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE); int fontStyle = getInt(style, mxConstants.STYLE_FONTSTYLE); int swingFontStyle = ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) ? Font.BOLD : Font.PLAIN; swingFontStyle += ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) ? Font.ITALIC : Font.PLAIN; return new Font(fontFamily, swingFontStyle, (int) (fontSize * scale)); } /** * */ public static String hexString(Color color) { int r = color.getRed(); int g = color.getGreen(); int b = color.getBlue(); return String.format("#%02X%02X%02X", r, g, b); } /** * Convert a string representing a 24/32bit hex color value into a Color * object. The following color names are also supported: white, black, red, * green, blue, orange, yellow, pink, turquoise, gray and none (null). * Examples of possible hex color values are: #C3D9FF, #6482B9 and #774400, * but note that you do not include the "#" in the string passed in * * @param colorString * the 24/32bit hex string value (ARGB) * @return java.awt.Color (24bit RGB on JDK 1.1, 24/32bit ARGB on JDK1.2) * @exception NumberFormatException * if the specified string cannot be interpreted as a * hexidecimal integer */ public static Color parseColor(String colorString) throws NumberFormatException { if (colorString.equalsIgnoreCase("white")) { return Color.white; } else if (colorString.equalsIgnoreCase("black")) { return Color.black; } else if (colorString.equalsIgnoreCase("red")) { return Color.red; } else if (colorString.equalsIgnoreCase("green")) { return Color.green; } else if (colorString.equalsIgnoreCase("blue")) { return Color.blue; } else if (colorString.equalsIgnoreCase("orange")) { return Color.orange; } else if (colorString.equalsIgnoreCase("yellow")) { return Color.yellow; } else if (colorString.equalsIgnoreCase("pink")) { return Color.pink; } else if (colorString.equalsIgnoreCase("turqoise")) { return new Color(0, 255, 255); } else if (colorString.equalsIgnoreCase("gray")) { return Color.gray; } else if (colorString.equalsIgnoreCase("none")) { return null; } int value; try { value = (int) Long.parseLong(colorString, 16); } catch (NumberFormatException nfe) { value = Long.decode(colorString).intValue(); } return new Color(value); } /** * Returns a hex representation for the given color. * * @param color * Color to return the hex string for. * @return Returns a hex string for the given color. */ public static String getHexColorString(Color color) { return Integer.toHexString((color.getRGB() & 0x00FFFFFF) | (color.getAlpha() << 24)); } /** * Reads the given filename into a string. * * @param filename * Name of the file to be read. * @return Returns a string representing the file contents. * @throws IOException */ public static String readFile(String filename) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream(filename))); StringBuffer result = new StringBuffer(); String tmp = reader.readLine(); while (tmp != null) { result.append(tmp + "\n"); tmp = reader.readLine(); } reader.close(); return result.toString(); } /** * Writes the given string into the given file. * * @param contents * String representing the file contents. * @param filename * Name of the file to be written. * @throws IOException */ public static void writeFile(String contents, String filename) throws IOException { FileWriter fw = new FileWriter(filename); fw.write(contents); fw.flush(); fw.close(); } /** * Returns the Md5 hash for the given text. * * @param text * String whose Md5 hash should be returned. * @return Returns the Md5 hash for the given text. */ public static String getMd5Hash(String text) { StringBuffer result = new StringBuffer(32); try { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(text.getBytes()); Formatter f = new Formatter(result); byte[] digest = md5.digest(); for (int i = 0; i < digest.length; i++) { f.format("%02x", new Object[] { new Byte(digest[i]) }); } } catch (NoSuchAlgorithmException ex) { ex.printStackTrace(); } return result.toString(); } /** * Returns true if the user object is an XML node with the specified type * and and the optional attribute has the specified value or is not * specified. * * @param value * Object that should be examined as a node. * @param nodeName * String that specifies the node name. * @return Returns true if the node name of the user object is equal to the * given type. */ public static boolean isNode(Object value, String nodeName) { return isNode(value, nodeName, null, null); } /** * Returns true if the given value is an XML node with the node name and if * the optional attribute has the specified value. * * @param value * Object that should be examined as a node. * @param nodeName * String that specifies the node name. * @param attributeName * Optional attribute name to check. * @param attributeValue * Optional attribute value to check. * @return Returns true if the value matches the given conditions. */ public static boolean isNode(Object value, String nodeName, String attributeName, String attributeValue) { if (value instanceof Element) { Element element = (Element) value; if (nodeName == null || element.getNodeName().equalsIgnoreCase(nodeName)) { String tmp = (attributeName != null) ? element .getAttribute(attributeName) : null; return attributeName == null || (tmp != null && tmp.equals(attributeValue)); } } return false; } /** * * @param g * @param antiAlias * @param textAntiAlias */ public static void setAntiAlias(Graphics2D g, boolean antiAlias, boolean textAntiAlias) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, (antiAlias) ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, (textAntiAlias) ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); } /** * Clears the given area of the specified graphics object with the given * color or makes the region transparent. */ public static void clearRect(Graphics2D g, Rectangle rect, Color background) { if (background != null) { g.setColor(background); g.fillRect(rect.x, rect.y, rect.width, rect.height); } else { g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f)); g.fillRect(rect.x, rect.y, rect.width, rect.height); g.setComposite(AlphaComposite.SrcOver); } } /** * Creates a buffered image for the given parameters. If there is not enough * memory to create the image then a OutOfMemoryError is thrown. */ public static BufferedImage createBufferedImage(int w, int h, Color background) throws OutOfMemoryError { BufferedImage result = null; if (w > 0 && h > 0) { // Checks if there is enough memory for allocating the buffer Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); long allocatedMemory = runtime.totalMemory(); long freeMemory = runtime.freeMemory(); long totalFreeMemory = (freeMemory + (maxMemory - allocatedMemory)) / 1024; int bytes = 4; // 1 if indexed long memoryRequired = w * h * bytes / 1024; if (memoryRequired <= totalFreeMemory) { int type = (background != null) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; result = new BufferedImage(w, h, type); // Clears background if (background != null) { Graphics2D g2 = result.createGraphics(); clearRect(g2, new Rectangle(w, h), background); g2.dispose(); } } else { throw new OutOfMemoryError("Not enough memory for image (" + w + " x " + h + ")"); } } return result; } /** * Loads an image from the local filesystem, a data URI or any other URL. */ public static BufferedImage loadImage(String url) { BufferedImage img = null; if (url != null) { // Parses data URIs of the form data:image/format;base64,xxx if (url.startsWith("data:image/")) { try { int comma = url.indexOf(','); byte[] data = mxBase64.decode(url.substring(comma + 1)); ByteArrayInputStream is = new ByteArrayInputStream(data); img = ImageIO.read(is); } catch (Exception e1) { // ignore } } else { URL realUrl = null; try { realUrl = new URL(url); } catch (Exception e) { realUrl = mxUtils.class.getResource(url); } if (realUrl != null) { try { img = ImageIO.read(realUrl); } catch (Exception e1) { // ignore } } } } return img; } /** * Creates a table for the given text using the given document to create the * DOM nodes. Returns the outermost table node. */ public static Element createTable(Document document, String text, int x, int y, int w, int h, double scale, Map<String, Object> style) { // Does not use a textbox as this must go inside another VML shape Element table = document.createElement("table"); if (text != null && text.length() > 0) { Element tr = document.createElement("tr"); Element td = document.createElement("td"); table.setAttribute("cellspacing", "0"); table.setAttribute("border", "0"); td.setAttribute("align", mxUtils.getString(style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER)); String fontColor = getString(style, mxConstants.STYLE_FONTCOLOR, "black"); String fontFamily = getString(style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILIES); int fontSize = (int) (getInt(style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * scale); String s = "position:absolute;" + "left:" + String.valueOf(x) + "px;" + "top:" + String.valueOf(y) + "px;" + "width:" + String.valueOf(w) + "px;" + "height:" + String.valueOf(h) + "px;" + "font-size:" + String.valueOf(fontSize) + "px;" + "font-family:" + fontFamily + ";" + "color:" + fontColor + ";"; if (mxUtils.getString(style, mxConstants.STYLE_WHITE_SPACE, "nowrap").equals("wrap")) { s += "whiteSpace:wrap;"; } // Applies the background color String background = getString(style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR); if (background != null) { s += "background:" + background + ";"; } // Applies the border color String border = getString(style, mxConstants.STYLE_LABEL_BORDERCOLOR); if (border != null) { s += "border:" + border + " solid 1pt;"; } // Applies the opacity float opacity = getFloat(style, mxConstants.STYLE_TEXT_OPACITY, 100); if (opacity < 100) { // Adds all rules (first for IE) s += "filter:alpha(opacity=" + opacity + ");"; s += "opacity:" + (opacity / 100) + ";"; } td.setAttribute("style", s); String[] lines = text.split("\n"); for (int i = 0; i < lines.length; i++) { td.appendChild(document.createTextNode(lines[i])); td.appendChild(document.createElement("br")); } tr.appendChild(td); table.appendChild(tr); } return table; } /** * Returns a new, empty DOM document. * * @return Returns a new DOM document. */ public static Document createDocument() { Document result = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); DocumentBuilder parser = factory.newDocumentBuilder(); result = parser.newDocument(); } catch (Exception e) { System.out.println(e.getMessage()); } return result; } /** * Creates a new SVG document for the given width and height. */ public static Document createSvgDocument(int width, int height) { Document document = createDocument(); Element root = document.createElement("svg"); String w = String.valueOf(width); String h = String.valueOf(height); root.setAttribute("width", w); root.setAttribute("height", h); root.setAttribute("viewBox", "0 0 " + w + " " + h); root.setAttribute("version", "1.1"); root.setAttribute("xmlns", mxConstants.NS_SVG); root.setAttribute("xmlns:xlink", mxConstants.NS_XLINK); document.appendChild(root); return document; } /** * */ public static Document createVmlDocument() { Document document = createDocument(); Element root = document.createElement("html"); root.setAttribute("xmlns:v", "urn:schemas-microsoft-com:vml"); root.setAttribute("xmlns:o", "urn:schemas-microsoft-com:office:office"); document.appendChild(root); Element head = document.createElement("head"); Element style = document.createElement("style"); style.setAttribute("type", "text/css"); style.appendChild(document .createTextNode("<!-- v\\:* {behavior: url(#default#VML);} -->")); head.appendChild(style); root.appendChild(head); Element body = document.createElement("body"); root.appendChild(body); return document; } /** * Returns a document with a HTML node containing a HEAD and BODY node. */ public static Document createHtmlDocument() { Document document = createDocument(); Element root = document.createElement("html"); document.appendChild(root); Element head = document.createElement("head"); root.appendChild(head); Element body = document.createElement("body"); root.appendChild(body); return document; } /** * Returns a new, empty DOM document. * * @return Returns a new DOM document. */ public static String createHtmlDocument(Map<String, Object> style, String text) { return createHtmlDocument(style, text, 1); } /** * Returns a new, empty DOM document. * * @return Returns a new DOM document. */ public static String createHtmlDocument(Map<String, Object> style, String text, double scale) { StringBuffer css = new StringBuffer(); css.append("font-family:" + getString(style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILIES) + ";"); css.append("font-size:" + (int) (getInt(style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * scale) + " pt;"); String color = mxUtils.getString(style, mxConstants.STYLE_FONTCOLOR); if (color != null) { css.append("color:" + color + ";"); } int fontStyle = mxUtils.getInt(style, mxConstants.STYLE_FONTSTYLE); if ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { css.append("font-weight:bold;"); } if ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { css.append("font-style:italic;"); } if ((fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { css.append("text-decoration:underline;"); } String align = getString(style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT); if (align.equals(mxConstants.ALIGN_CENTER)) { css.append("text-align:center;"); } else if (align.equals(mxConstants.ALIGN_RIGHT)) { css.append("text-align:right;"); } return "<html><body style=\"" + css.toString() + "\">" + text + "</body></html>"; } /** * Returns a new, empty DOM document. * * @return Returns a new DOM document. */ public static HTMLDocument createHtmlDocumentObject( Map<String, Object> style, double scale) { // Applies the font settings HTMLDocument document = new HTMLDocument(); StringBuffer rule = new StringBuffer("body {"); rule.append(" font-family: " + getString(style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILIES) + " ; "); rule.append(" font-size: " + (int) (getInt(style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * scale) + " pt ;"); String color = mxUtils.getString(style, mxConstants.STYLE_FONTCOLOR); if (color != null) { rule.append("color: " + color + " ; "); } int fontStyle = mxUtils.getInt(style, mxConstants.STYLE_FONTSTYLE); if ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { rule.append(" font-weight: bold ; "); } if ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { rule.append(" font-style: italic ; "); } if ((fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { rule.append(" text-decoration: underline ; "); } String align = getString(style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT); if (align.equals(mxConstants.ALIGN_CENTER)) { rule.append(" text-align: center ; "); } else if (align.equals(mxConstants.ALIGN_RIGHT)) { rule.append(" text-align: right ; "); } rule.append(" } "); document.getStyleSheet().addRule(rule.toString()); return document; } /** * Returns a new DOM document for the given URI. * * @param uri * URI to parse into the document. * @return Returns a new DOM document for the given URI. */ public static Document loadDocument(String uri) { try { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory .newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); return docBuilder.parse(uri); } catch (Exception e) { e.printStackTrace(); } return null; } /** * Returns a new document for the given XML string. * * @param xml * String that represents the XML data. * @return Returns a new XML document. * @deprecated as of 31.08.2010. Use parseXML(String xml) instead */ public static Document parse(String xml) { return mxUtils.parseXml(xml); } /** * Returns a new document for the given XML string. * * @param xml * String that represents the XML data. * @return Returns a new XML document. */ public static Document parseXml(String xml) { try { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory .newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); return docBuilder.parse(new InputSource(new StringReader(xml))); } catch (Exception e) { e.printStackTrace(); } return null; } /** * Evaluates a Java expression as a class member using mxCodecRegistry. The * range of supported expressions is limited to static class members such as * mxEdgeStyle.ElbowConnector. */ public static Object eval(String expression) { int dot = expression.lastIndexOf("."); if (dot > 0) { Class<?> clazz = mxCodecRegistry.getClassForName(expression .substring(0, dot)); if (clazz != null) { try { return clazz.getField(expression.substring(dot + 1)).get( null); } catch (Exception e) { // ignore } } } return expression; } /** * Returns the first node where attr equals value. This implementation does * not use XPath. */ public static Node findNode(Node node, String attr, String value) { String tmp = (node instanceof Element) ? ((Element) node) .getAttribute(attr) : null; if (tmp != null && tmp.equals(value)) { return node; } node = node.getFirstChild(); while (node != null) { Node result = findNode(node, attr, value); if (result != null) { return result; } node = node.getNextSibling(); } return null; } /** * Returns a single node that matches the given XPath expression. * * @param doc * Document that contains the nodes. * @param expression * XPath expression to be matched. * @return Returns a single node matching the given expression. */ public static Node selectSingleNode(Document doc, String expression) { try { XPath xpath = XPathFactory.newInstance().newXPath(); return (Node) xpath.evaluate(expression, doc, XPathConstants.NODE); } catch (XPathExpressionException e) { // ignore } return null; } /** * Converts the ampersand, quote, prime, less-than and greater-than * characters to their corresponding HTML entities in the given string. */ public static String htmlEntities(String text) { return text.replaceAll("&", "&").replaceAll("\"", """) .replaceAll("'", "′").replaceAll("<", "<") .replaceAll(">", ">"); } /** * Returns a string that represents the given node. * * @param node * Node to return the XML for. * @return Returns an XML string. */ public static String getXml(Node node) { try { Transformer tf = TransformerFactory.newInstance().newTransformer(); tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); StreamResult dest = new StreamResult(new StringWriter()); tf.transform(new DOMSource(node), dest); return dest.getWriter().toString(); } catch (Exception e) { // ignore } return ""; } /** * Returns a pretty-printed XML string for the given node. * * @param node * Node to return the XML for. * @return Returns a formatted XML string. */ public static String getPrettyXml(Node node) { return getPrettyXml(node, " ", ""); } /** * Returns a pretty-printed XML string for the given node. Note that this * string should only be used for humans to read (eg. debug output) but not * for further processing as it does not use built-in mechanisms. * * @param node * Node to return the XML for. * @param tab * String to be used for indentation of inner nodes. * @param indent * Current indentation for the node. * @return Returns a formatted XML string. */ public static String getPrettyXml(Node node, String tab, String indent) { StringBuffer result = new StringBuffer(); if (node != null) { if (node.getNodeType() == Node.TEXT_NODE) { result.append(node.getNodeValue()); } else { result.append(indent + "<" + node.getNodeName()); NamedNodeMap attrs = node.getAttributes(); if (attrs != null) { for (int i = 0; i < attrs.getLength(); i++) { String value = attrs.item(i).getNodeValue(); value = mxUtils.htmlEntities(value); result.append(" " + attrs.item(i).getNodeName() + "=\"" + value + "\""); } } Node tmp = node.getFirstChild(); if (tmp != null) { result.append(">\n"); while (tmp != null) { result.append(getPrettyXml(tmp, tab, indent + tab)); tmp = tmp.getNextSibling(); } result.append(indent + "</" + node.getNodeName() + ">\n"); } else { result.append("/>\n"); } } } return result.toString(); } }