/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * DotParser.java * Copyright (C) 2003 University of Waikato, Hamilton, New Zealand * */ package weka.gui.graphvisualizer; import java.io.Reader; import java.io.StreamTokenizer; import java.io.BufferedReader; import java.io.IOException; import java.io.FileWriter; import weka.core.FastVector; import weka.gui.graphvisualizer.GraphNode; import weka.gui.graphvisualizer.GraphEdge; /** * This class parses input in DOT format, and * builds the datastructures that are passed to it. * It is NOT 100% compatible with the DOT format. The * GraphNode and GraphEdge classes do not have any provision * for dealing with different colours or shapes of nodes, * there can however, be a different label and ID for a * node. It also does not do anything for labels for * edges. However, this class won't crash or throw an * exception if it encounters any of the above * attributes of an edge or a node. This class however, * won't be able to deal with things like subgraphs and * grouping of nodes. * * @author Ashraf M. Kibriya (amk14@cs.waikato.ac.nz) * @version $Revision: 1.4 $ - 23 Apr 2003 - Initial version (Ashraf M. Kibriya) */ public class DotParser implements GraphConstants { /** These holds the nodes and edges of the graph */ protected FastVector m_nodes, m_edges; /** This is the input containing DOT stream to be parsed */ protected Reader m_input; /** This holds the name of the graph if there is any otherwise it is null */ protected String m_graphName; /** * * Dot parser Constructor * * @param input - The input, if passing in a string then * encapsulate that in String reader object * @param nodes - Vector to put in GraphNode objects, * corresponding to the nodes parsed in from * the input * @param edges - Vector to put in GraphEdge objects, * corresponding to the edges parsed in from * the input */ public DotParser(Reader input, FastVector nodes, FastVector edges) { m_nodes = nodes; m_edges = edges; m_input = input; } /** * This method parses the string or the InputStream that we * passed in through the constructor and builds up the * m_nodes and m_edges vectors * * @return - returns the name of the graph */ public String parse() { StreamTokenizer tk = new StreamTokenizer(new BufferedReader(m_input)); setSyntax(tk); graph(tk); return m_graphName; } /** * This method sets the syntax of the StreamTokenizer. * i.e. set the whitespace, comment and delimit chars. * */ protected void setSyntax(StreamTokenizer tk) { tk.resetSyntax(); tk.eolIsSignificant(false); tk.slashStarComments(true); tk.slashSlashComments(true); tk.whitespaceChars(0,' '); tk.wordChars(' '+1,'\u00ff'); tk.ordinaryChar('['); tk.ordinaryChar(']'); tk.ordinaryChar('{'); tk.ordinaryChar('}'); tk.ordinaryChar('-'); tk.ordinaryChar('>'); tk.ordinaryChar('/'); tk.ordinaryChar('*'); tk.quoteChar('"'); tk.whitespaceChars(';',';'); tk.ordinaryChar('='); } /************************************************************* * * Following methods parse the DOT input and mimic the DOT * language's grammar in their structure * ************************************************************* */ protected void graph(StreamTokenizer tk) { try { tk.nextToken(); if(tk.ttype==tk.TT_WORD) { if(tk.sval.equalsIgnoreCase("digraph")) { tk.nextToken(); if(tk.ttype==tk.TT_WORD) { m_graphName = tk.sval; tk.nextToken(); } while(tk.ttype!='{') { System.err.println("Error at line "+tk.lineno()+" ignoring token "+ tk.sval); tk.nextToken(); if(tk.ttype==tk.TT_EOF) return; } stmtList(tk); } else if(tk.sval.equalsIgnoreCase("graph")) System.err.println("Error. Undirected graphs cannot be used"); else System.err.println("Error. Expected graph or digraph at line "+ tk.lineno()); } } catch(Exception ex) { ex.printStackTrace(); } //int tmpMatrix[][] = new int[m_nodes.size()][m_nodes.size()]; //for(int i=0; i<m_edges.size(); i++) // tmpMatrix[((GraphEdge)m_edges.elementAt(i)).src] // [((GraphEdge)m_edges.elementAt(i)).dest] = // ((GraphEdge)m_edges.elementAt(i)).type; //for(int i=0; i<m_nodes.size(); i++) { // GraphNode n = (GraphNode)m_nodes.elementAt(i); // n.edges = tmpMatrix[i]; //} //Adding parents, and those edges to a node which are coming out from it int tmpEdges[], noOfEdgesOfNode[]=new int[m_nodes.size()]; int noOfPrntsOfNode[]=new int[m_nodes.size()]; for(int i=0; i<m_edges.size(); i++) { GraphEdge e = (GraphEdge)m_edges.elementAt(i); noOfEdgesOfNode[e.src]++; noOfPrntsOfNode[e.dest]++; } for(int i=0; i<m_edges.size(); i++) { GraphEdge e = (GraphEdge)m_edges.elementAt(i); GraphNode n = (GraphNode)m_nodes.elementAt(e.src); GraphNode n2 = (GraphNode)m_nodes.elementAt(e.dest); if(n.edges==null) { n.edges = new int[noOfEdgesOfNode[e.src]][2]; for(int k=0; k<n.edges.length; k++) n.edges[k][1]=0; } if(n2.prnts==null) { n2.prnts = new int[noOfPrntsOfNode[e.dest]]; for(int k=0; k<n2.prnts.length; k++) n2.prnts[k]=-1; } int k=0; while(n.edges[k][1]!=0) k++; n.edges[k][0] = e.dest; n.edges[k][1] = e.type; k=0; while(n2.prnts[k]!=-1) k++; n2.prnts[k] = e.src; } } protected void stmtList(StreamTokenizer tk) throws Exception{ tk.nextToken(); if(tk.ttype=='}' || tk.ttype==tk.TT_EOF) return; else { stmt(tk); stmtList(tk); } } protected void stmt(StreamTokenizer tk) { //tk.nextToken(); if(tk.sval.equalsIgnoreCase("graph") || tk.sval.equalsIgnoreCase("node") || tk.sval.equalsIgnoreCase("edge") ) ; //attribStmt(k); else { try { nodeID(tk); int nodeindex= m_nodes.indexOf(new GraphNode(tk.sval, null)); tk.nextToken(); if(tk.ttype == '[') nodeStmt(tk, nodeindex); else if(tk.ttype == '-') edgeStmt(tk, nodeindex); else System.err.println("error at lineno "+tk.lineno()+" in stmt"); } catch(Exception ex) { System.err.println("error at lineno "+tk.lineno()+" in stmtException"); ex.printStackTrace(); } } } protected void nodeID(StreamTokenizer tk) throws Exception{ if(tk.ttype=='"' || tk.ttype==tk.TT_WORD || (tk.ttype>='a' && tk.ttype<='z') || (tk.ttype>='A' && tk.ttype<='Z')) { if(m_nodes!=null && !(m_nodes.contains( new GraphNode(tk.sval, null))) ) { m_nodes.addElement( new GraphNode(tk.sval, tk.sval) ); //System.out.println("Added node >"+tk.sval+"<"); } } else { throw new Exception(); } //tk.nextToken(); } protected void nodeStmt(StreamTokenizer tk, final int nindex) throws Exception { tk.nextToken(); GraphNode temp = (GraphNode) m_nodes.elementAt(nindex); if(tk.ttype==']' || tk.ttype==tk.TT_EOF) return; else if(tk.ttype==tk.TT_WORD) { if(tk.sval.equalsIgnoreCase("label")) { tk.nextToken(); if(tk.ttype=='=') { tk.nextToken(); if(tk.ttype==tk.TT_WORD || tk.ttype=='"') temp.lbl = tk.sval; else { System.err.println("couldn't find label at line "+tk.lineno()); tk.pushBack(); } } else { System.err.println("couldn't find label at line "+tk.lineno()); tk.pushBack(); } } else if(tk.sval.equalsIgnoreCase("color")){ tk.nextToken(); if(tk.ttype=='=') { tk.nextToken(); if(tk.ttype==tk.TT_WORD || tk.ttype=='"') ; else { System.err.println("couldn't find color at line "+tk.lineno()); tk.pushBack(); } } else { System.err.println("couldn't find color at line "+tk.lineno()); tk.pushBack(); } } else if(tk.sval.equalsIgnoreCase("style")) { tk.nextToken(); if(tk.ttype=='=') { tk.nextToken(); if(tk.ttype==tk.TT_WORD || tk.ttype=='"') ; else { System.err.println("couldn't find style at line "+tk.lineno()); tk.pushBack(); } } else { System.err.println("couldn't find style at line "+tk.lineno()); tk.pushBack(); } } } nodeStmt(tk, nindex); } protected void edgeStmt(StreamTokenizer tk, final int nindex) throws Exception { tk.nextToken(); GraphEdge e=null; if(tk.ttype=='>') { tk.nextToken(); if(tk.ttype=='{') { while(true) { tk.nextToken(); if(tk.ttype=='}') break; else { nodeID(tk); e = new GraphEdge(nindex, m_nodes.indexOf( new GraphNode(tk.sval, null) ), DIRECTED); if( m_edges!=null && !(m_edges.contains(e)) ) { m_edges.addElement( e ); //System.out.println("Added edge from "+ // ((GraphNode)(m_nodes.elementAt(nindex))).ID+ // " to "+ // ((GraphNode)(m_nodes.elementAt(e.dest))).ID); } } } } else { nodeID(tk); e = new GraphEdge(nindex, m_nodes.indexOf( new GraphNode(tk.sval, null) ), DIRECTED); if( m_edges!=null && !(m_edges.contains(e)) ) { m_edges.addElement( e ); //System.out.println("Added edge from "+ // ((GraphNode)(m_nodes.elementAt(nindex))).ID+" to "+ // ((GraphNode)(m_nodes.elementAt(e.dest))).ID); } } } else if(tk.ttype=='-') { System.err.println("Error at line "+tk.lineno()+ ". Cannot deal with undirected edges"); if(tk.ttype==tk.TT_WORD) tk.pushBack(); return; } else { System.err.println("Error at line "+tk.lineno()+" in edgeStmt"); if(tk.ttype==tk.TT_WORD) tk.pushBack(); return; } tk.nextToken(); if(tk.ttype=='[') edgeAttrib(tk, e); else tk.pushBack(); } protected void edgeAttrib(StreamTokenizer tk, final GraphEdge e) throws Exception { tk.nextToken(); if(tk.ttype==']' || tk.ttype==tk.TT_EOF) return; else if(tk.ttype==tk.TT_WORD) { if(tk.sval.equalsIgnoreCase("label")) { tk.nextToken(); if(tk.ttype=='=') { tk.nextToken(); if(tk.ttype==tk.TT_WORD || tk.ttype=='"') System.err.println("found label "+tk.sval);//e.lbl = tk.sval; else { System.err.println("couldn't find label at line "+tk.lineno()); tk.pushBack(); } } else { System.err.println("couldn't find label at line "+tk.lineno()); tk.pushBack(); } } else if(tk.sval.equalsIgnoreCase("color")) { tk.nextToken(); if(tk.ttype=='=') { tk.nextToken(); if(tk.ttype==tk.TT_WORD || tk.ttype=='"') ; else { System.err.println("couldn't find color at line "+tk.lineno()); tk.pushBack(); } } else { System.err.println("couldn't find color at line "+tk.lineno()); tk.pushBack(); } } else if(tk.sval.equalsIgnoreCase("style")) { tk.nextToken(); if(tk.ttype=='=') { tk.nextToken(); if(tk.ttype==tk.TT_WORD || tk.ttype=='"') ; else { System.err.println("couldn't find style at line "+tk.lineno()); tk.pushBack(); } } else { System.err.println("couldn't find style at line "+tk.lineno()); tk.pushBack(); } } } edgeAttrib(tk, e); } /** * * This method saves a graph in a file in DOT format. * However, if reloaded in GraphVisualizer we would need * to layout the graph again. * * @param filename - The name of the file to write in. (will overwrite) * @param graphName - The name of the graph * @param nodes - Vector containing all the nodes * @param edges - Vector containing all the edges */ public static void writeDOT(String filename, String graphName, FastVector nodes, FastVector edges) { try { FileWriter os = new FileWriter(filename); os.write("digraph ", 0, ("digraph ").length()); if(graphName!=null) os.write(graphName+" ", 0, graphName.length()+1); os.write("{\n", 0, ("{\n").length()); GraphEdge e; for(int i=0; i<edges.size(); i++) { e = (GraphEdge) edges.elementAt(i); os.write(((GraphNode)nodes.elementAt(e.src)).ID, 0, ((GraphNode)nodes.elementAt(e.src)).ID.length()); os.write("->", 0, ("->").length() ); os.write(((GraphNode)nodes.elementAt(e.dest)).ID+"\n", 0, ((GraphNode)nodes.elementAt(e.dest)).ID.length()+1); } os.write("}\n", 0, ("}\n").length()); os.close(); } catch(IOException ex) { ex.printStackTrace(); } } } // DotParser