All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.ethz.sn.visone3.io.graphml.GraphmlSink Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of netroles.
 *
 * netroles 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 3 of the License, or
 * (at your option) any later version.
 *
 * netroles 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 netroles.  If not, see .
 */

package ch.ethz.sn.visone3.io.graphml;

import ch.ethz.sn.visone3.io.AbstractSink;
import ch.ethz.sn.visone3.lang.ConstMapping;
import ch.ethz.sn.visone3.networks.Edge;
import ch.ethz.sn.visone3.networks.Network;
import ch.ethz.sn.visone3.networks.Networks;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.UnaryOperator;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * Writes a network as GraphML file using the standards described by the GraphML Primer:
 * (http://graphml.graphdrawing.org/primer/graphml-primer.html)
 *
 * 
 * <?xml version="1.0" encoding="UTF-8"?>
 * <graphml ...>
 *  <key ...>
 *    <default >
 *    ...
 *    <default .../>
 *  <key/>
 *   ...
 *   <graph id="..." edgedefault="..." ...>
 *     <node id="...">
 *       <data key="...">...</data>
 *       ...
 *     </node>
 *     ...
 *     <edge id="..." source="..." target="...">
 *       <data key="...">...</data>
 *       ...
 *     </edge>
 *     ...
 *     <data key="...">...</data>
 *     ...
 *   </graph>
 *   <data key="...">...</data>
 *   ...
 * </graphml>
 * 
*/ public class GraphmlSink extends AbstractSink implements AutoCloseable { private static final String PREFIX_NODE = "n"; private static final String PREFIX_EDGE = "e"; private static final String PREFIX_KEY = "k"; /** * Hints accepted by the graphml sink. */ public enum Hint { /** * Boolean hint whether to include additional hints to a parser that could * speed up reading the GraphML file. */ PARSE_INFO } private final OutputStream out; private final Document doc; private final Map graphDefault; private final Map nodeDefault; private final Map edgeDefault; private final Map> edgeAttr; private final Map> nodeAttr; /** * Mapping global, monadic and dyadic mapping names to unique data keys. */ private final Map keyToId = new LinkedHashMap<>(); private final UnaryOperator keyIdGenerator = (name) -> PREFIX_KEY + keyToId.size(); private boolean writeParseInfo = false; private Network graph; /** * Constructs a new sink. * * @param out the output stream to write to. * @throws ParserConfigurationException if some exception occured while * constructing the XML document. */ public GraphmlSink(final OutputStream out) throws ParserConfigurationException { this.out = out; graphDefault = new LinkedHashMap<>(); nodeDefault = new LinkedHashMap<>(); edgeDefault = new LinkedHashMap<>(); edgeAttr = new LinkedHashMap<>(); nodeAttr = new LinkedHashMap<>(); final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); final DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); doc = docBuilder.newDocument(); } @Override public void hint(final String key, final String value) { final Hint hint = Hint.valueOf(key); switch (hint) { case PARSE_INFO: writeParseInfo = Boolean.parseBoolean(value); break; default: throw new IllegalArgumentException("unknown hint " + key); } } // public void setWriteParseInfo(final boolean writeParseInfo) { // this.writeParseInfo = writeParseInfo; // } // // public void setKeyIdGenerator(final UnaryOperator keyIdGenerator) { // this.keyIdGenerator = keyIdGenerator; // } @Override public void close() throws IOException { try { // check attribute dimensions Objects.requireNonNull(graph); for (final Map.Entry> e : nodeAttr.entrySet()) { Networks.requireVertexMapping(graph, e.getValue()); } for (final Map.Entry> e : edgeAttr.entrySet()) { Networks.requireLinkMapping(graph, e.getValue()); } write(); // write the content into xml file final Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //initialize StreamResult with File object to save to file final StreamResult result = new StreamResult(out); final DOMSource source = new DOMSource(doc); transformer.transform(source, result); } catch (final TransformerException te) { throw new IOException(te); } finally { out.flush(); out.close(); } } private void write() { final Element root = writeHead(doc); // writeKeysForDefaults(root); writeGlobal(root); writeKeys(root, GraphmlTokens.EDGE, edgeDefault, edgeAttr); writeKeys(root, GraphmlTokens.NODE, nodeDefault, nodeAttr); // graphstream (http://graphstream-project.org/) chokes on parse info final Element graphElmnt = writeGraph(root); writeNodes(graphElmnt); writeEdges(graphElmnt); } private void writeEdges(final Element graphElmnt) { int index = 0; Element edge; final Iterable rel = graph.isDirected() ? graph.asDirectedGraph().getEdges() : graph.asUndirectedGraph().getEdges(); for (final Edge r : rel) { edge = graphElmnt.getOwnerDocument().createElement(GraphmlTokens.EDGE); graphElmnt.appendChild(edge); edge.setAttribute(GraphmlTokens.ID, PREFIX_EDGE + index); edge.setAttribute(GraphmlTokens.SOURCE, PREFIX_NODE + r.getSource()); edge.setAttribute(GraphmlTokens.TARGET, PREFIX_NODE + r.getTarget()); writeDataFor(edge, GraphmlTokens.EDGE, edgeDefault, edgeAttr, index); index++; } } private void writeNodes(final Element graphElmnt) { final int n = graph.asRelation().countUnionDomain(); Element node; for (int i = 0; i < n; i++) { node = graphElmnt.getOwnerDocument().createElement(GraphmlTokens.NODE); graphElmnt.appendChild(node); node.setAttribute(GraphmlTokens.ID, PREFIX_NODE + i); writeDataFor(node, GraphmlTokens.NODE, nodeDefault, nodeAttr, i); } } /** * Writes attribute data. * @param token The parent token type. * @param index Index of the parent element. */ private void writeDataFor( final Element elem, final String token, final Map def, final Map> map, final int index ) { for (final Map.Entry> e : map.entrySet()) { final Object defValue = def.get(e.getKey()); final Object value = e.getValue().get(index); // write if it exists and is not the default value if (value != null && (!def.containsKey(e.getKey()) || !Objects.equals(value, defValue))) { final Element data = doc.createElement(GraphmlTokens.DATA); elem.appendChild(data); data.setAttribute(GraphmlTokens.KEY, keyToId.get(e.getKey() + token)); data.appendChild(doc.createTextNode(value.toString())); } } } /** * Writes the graph tag with the corresponding data. */ private Element writeGraph(final Element parent) { final Element graphElmnt = parent.getOwnerDocument().createElement(GraphmlTokens.GRAPH); parent.appendChild(graphElmnt); final Attr attr = parent.getOwnerDocument().createAttribute(GraphmlTokens.EDGEDEFAULT); if (graph.isDirected()) { attr.setValue(GraphmlTokens.DIRECTED); } else { attr.setValue(GraphmlTokens.UNDIRECTED); } graphElmnt.setAttributeNode(attr); // parse.nodes and parse.edges if (writeParseInfo) { final int n = graph.asRelation().countUnionDomain(); final int m = graph.countDyadicIndices(); graphElmnt.setAttribute(GraphmlTokens.PARSE_NODES, String.valueOf(n)); graphElmnt.setAttribute(GraphmlTokens.PARSE_EDGES, String.valueOf(m)); //parse.nodeids parse.edgeids graphElmnt.setAttribute(GraphmlTokens.PARSE_EDGEIDS, GraphmlTokens.CANONICAL); graphElmnt.setAttribute(GraphmlTokens.PARSE_NODEIDS, GraphmlTokens.CANONICAL); //parse.order graphElmnt.setAttribute(GraphmlTokens.PARSE_ORDER, GraphmlTokens.NODESFIRST); } return graphElmnt; } /** * Graphml root element. */ private Element writeHead(final Document parent) { final Element root = parent.createElement(GraphmlTokens.GRAPHML); parent.appendChild(root); root.setAttribute("xmlns", "http://graphml.graphdrawing.org/xmlns"); root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); root.setAttribute("xsi:schemaLocation", "http://graphml.graphdrawing.org/xmlns " + "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"); return root; } private void writeGlobal(final Element parent) { for (final Map.Entry e : graphDefault.entrySet()) { final Element key = doc.createElement(GraphmlTokens.KEY); parent.appendChild(key); final Class ct = e.getValue().getClass(); key.setAttribute(GraphmlTokens.ATTR_TYPE, classToToken(ct)); key.setAttribute(GraphmlTokens.ID, keyToId.computeIfAbsent(e.getKey() + GraphmlTokens.GRAPH, keyIdGenerator)); key.setAttribute(GraphmlTokens.FOR, GraphmlTokens.GRAPH); key.setAttribute(GraphmlTokens.ATTR_NAME, e.getKey()); final Element def = doc.createElement(GraphmlTokens.DEFAULT); key.appendChild(def); def.appendChild(doc.createTextNode(e.getValue().toString())); } } /** * Write the data key definitions. */ private void writeKeys( final Element parent, final String token, final Map def, final Map> map ) { // union over keys final Set keys = new LinkedHashSet<>(); keys.addAll(def.keySet()); keys.addAll(map.keySet()); for (final String ek : keys) { final Element key = doc.createElement(GraphmlTokens.KEY); parent.appendChild(key); key.setAttribute(GraphmlTokens.ID, keyToId.computeIfAbsent(ek + token, keyIdGenerator)); key.setAttribute(GraphmlTokens.FOR, token); key.setAttribute(GraphmlTokens.ATTR_NAME, ek); if (def.containsKey(ek)) { final Class ct = def.get(ek).getClass(); key.setAttribute(GraphmlTokens.ATTR_TYPE, classToToken(ct)); // put fix default values in key tags. final Element defaultValue = doc.createElement(GraphmlTokens.DEFAULT); key.appendChild(defaultValue); final Object val = def.get(ek); defaultValue.appendChild(doc.createTextNode(val.toString())); } else { final Class ct = map.get(ek).getComponentType(); key.setAttribute(GraphmlTokens.ATTR_TYPE, classToToken(ct)); } } } @Override public void global(final String name, final Object value) { graphDefault.put(name, value); } @Override public void incidence(final Network network) { graph = network; } @Override public void node(final String name, final T def, final ConstMapping monadic) { if (def != null) { nodeDefault.put(name, def); } else { nodeDefault.remove(name); } if (monadic != null) { nodeAttr.put(name, monadic); } else { nodeAttr.remove(name); } } @Override public void link(final String name, final T def, final ConstMapping dyadic) { if (def != null) { edgeDefault.put(name, def); } else { edgeDefault.remove(name); } if (dyadic != null) { edgeAttr.put(name, dyadic); } else { edgeAttr.remove(name); } } private String classToToken(final Class componentType) { if (componentType == Integer.class || componentType == int.class) { return GraphmlTokens.INT; } if (componentType == String.class) { return GraphmlTokens.STRING; } if (componentType == Double.class || componentType == double.class) { return GraphmlTokens.DOUBLE; } return GraphmlTokens.STRING; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy