/**
* $Id: mxUtils.java,v 1.113 2011-07-25 20:45:46 gaudenz 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 * scale, size.height
* scale);
}
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(mxCellState terminal,
mxCellState edge, boolean source)
{
return getPortConstraints(terminal, edge, source,
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(mxCellState terminal,
mxCellState edge, boolean source, int defaultValue)
{
Object value = terminal.getStyle().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();
}
}