/**
* Copyright (c) 2004-2006 Regents of the University of California.
* See "license-prefuse.txt" for licensing terms.
*/
package prefuse.data.io;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.util.io.XMLWriter;
/**
* GraphWriter instance that writes a graph file formatted using the
* GraphML file format. GraphML is an XML format supporting graph
* structure and typed data schemas for both nodes and edges. For more
* information about the format, please see the
* <a href="http://graphml.graphdrawing.org/">GraphML home page</a>.
*
* <p>The GraphML spec only supports the data types <code>int</code>,
* <code>long</code>, <code>float</code>, <code>double</code>,
* <code>boolean</code>, and <code>string</code>. An exception will
* be thrown if a data type outside these allowed types is
* encountered.</p>
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class GraphMLWriter extends AbstractGraphWriter {
/**
* String tokens used in the GraphML format.
*/
public interface Tokens extends GraphMLReader.Tokens {
public static final String GRAPHML = "graphml";
public static final String GRAPHML_HEADER =
"<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\"\n"
+" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+" xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns\n"
+" http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd\">\n\n";
}
/**
* Map containing legal data types and their names in the GraphML spec
*/
private static final HashMap TYPES = new HashMap();
static {
TYPES.put(int.class, Tokens.INT);
TYPES.put(long.class, Tokens.LONG);
TYPES.put(float.class, Tokens.FLOAT);
TYPES.put(double.class, Tokens.DOUBLE);
TYPES.put(boolean.class, Tokens.BOOLEAN);
TYPES.put(String.class, Tokens.STRING);
}
/**
* @see prefuse.data.io.GraphWriter#writeGraph(prefuse.data.Graph, java.io.OutputStream)
*/
public void writeGraph(Graph graph, OutputStream os) throws DataIOException
{
// first, check the schemas to ensure GraphML compatibility
Schema ns = graph.getNodeTable().getSchema();
Schema es = graph.getEdgeTable().getSchema();
checkGraphMLSchema(ns);
checkGraphMLSchema(es);
XMLWriter xml = new XMLWriter(new PrintWriter(os));
xml.begin(Tokens.GRAPHML_HEADER, 2);
xml.comment("prefuse GraphML Writer | "
+ new Date(System.currentTimeMillis()));
// print the graph schema
printSchema(xml, Tokens.NODE, ns, null);
printSchema(xml, Tokens.EDGE, es, new String[] {
graph.getEdgeSourceField(), graph.getEdgeTargetField()
});
xml.println();
// print graph contents
xml.start(Tokens.GRAPH, Tokens.EDGEDEF,
graph.isDirected() ? Tokens.DIRECTED : Tokens.UNDIRECTED);
// print the nodes
xml.comment("nodes");
Iterator nodes = graph.nodes();
while ( nodes.hasNext() ) {
Node n = (Node)nodes.next();
if ( ns.getColumnCount() > 0 ) {
xml.start(Tokens.NODE, Tokens.ID, String.valueOf(n.getRow()));
for ( int i=0; i<ns.getColumnCount(); ++i ) {
String field = ns.getColumnName(i);
xml.contentTag(Tokens.DATA, Tokens.KEY, field,
n.getString(field));
}
xml.end();
} else {
xml.tag(Tokens.NODE, Tokens.ID, String.valueOf(n.getRow()));
}
}
// add a blank line
xml.println();
// print the edges
String[] attr = new String[]{Tokens.ID, Tokens.SOURCE, Tokens.TARGET};
String[] vals = new String[3];
xml.comment("edges");
Iterator edges = graph.edges();
while ( edges.hasNext() ) {
Edge e = (Edge)edges.next();
vals[0] = String.valueOf(e.getRow());
vals[1] = String.valueOf(e.getSourceNode().getRow());
vals[2] = String.valueOf(e.getTargetNode().getRow());
if ( es.getColumnCount() > 2 ) {
xml.start(Tokens.EDGE, attr, vals, 3);
for ( int i=0; i<es.getColumnCount(); ++i ) {
String field = es.getColumnName(i);
if ( field.equals(graph.getEdgeSourceField()) ||
field.equals(graph.getEdgeTargetField()) )
continue;
xml.contentTag(Tokens.DATA, Tokens.KEY, field,
e.getString(field));
}
xml.end();
} else {
xml.tag(Tokens.EDGE, attr, vals, 3);
}
}
xml.end();
// finish writing file
xml.finish("</"+Tokens.GRAPHML+">\n");
}
/**
* Print a table schema to a GraphML file
* @param xml the XMLWriter to write to
* @param group the data group (node or edge) for the schema
* @param s the schema
*/
private void printSchema(XMLWriter xml, String group, Schema s,
String[] ignore)
{
String[] attr = new String[] {Tokens.ID, Tokens.FOR,
Tokens.ATTRNAME, Tokens.ATTRTYPE };
String[] vals = new String[4];
OUTER:
for ( int i=0; i<s.getColumnCount(); ++i ) {
vals[0] = s.getColumnName(i);
for ( int j=0; ignore!=null && j<ignore.length; ++j ) {
if ( vals[0].equals(ignore[j]) )
continue OUTER;
}
vals[1] = group;
vals[2] = vals[0];
vals[3] = (String)TYPES.get(s.getColumnType(i));
Object dflt = s.getDefault(i);
if ( dflt == null ) {
xml.tag(Tokens.KEY, attr, vals, 4);
} else {
xml.start(Tokens.KEY, attr, vals, 4);
xml.contentTag(Tokens.DEFAULT, dflt.toString());
xml.end();
}
}
}
/**
* Checks if all Schema types are compatible with the GraphML specification.
* The GraphML spec only allows the types <code>int</code>,
* <code>long</code>, <code>float</code>, <code>double</code>,
* <code>boolean</code>, and <code>string</code>.
* @param s the Schema to check
*/
private void checkGraphMLSchema(Schema s) throws DataIOException {
for ( int i=0; i<s.getColumnCount(); ++i ) {
Class type = s.getColumnType(i);
if ( TYPES.get(type) == null ) {
throw new DataIOException("Data type unsupported by the "
+ "GraphML format: " + type.getName());
}
}
}
} // end of class GraphMLWriter