/** * 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