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

org.jgrapht.nio.gexf.GEXFExporter Maven / Gradle / Ivy

/*
 * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * See the CONTRIBUTORS.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the
 * GNU Lesser General Public License v2.1 or later
 * which is available at
 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
 */
package org.jgrapht.nio.gexf;

import org.jgrapht.*;
import org.jgrapht.nio.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

import javax.xml.transform.*;
import javax.xml.transform.sax.*;
import javax.xml.transform.stream.*;
import java.io.*;
import java.util.*;
import java.util.Map.*;
import java.util.function.*;

/**
 * Exports a graph as GEXF (Graph Exchange XML Format).
 *
 * 

* For a description of the format see * https://gephi.org/gexf/format/schema.html. A nice primer for the format is located at * https://gephi.org/gexf/1.2draft/gexf-12draft-primer.pdf. *

* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class GEXFExporter extends BaseExporter implements GraphExporter { private static final String LABEL_ATTRIBUTE_NAME = "label"; private static final String WEIGHT_ATTRIBUTE_NAME = "weight"; private static final String TYPE_ATTRIBUTE_NAME = "type"; private static final Set VERTEX_RESERVED_ATTRIBUTES = Set.of("id", LABEL_ATTRIBUTE_NAME); private static final Set EDGE_RESERVED_ATTRIBUTES = Set.of("id", "type", LABEL_ATTRIBUTE_NAME, "source", "target", WEIGHT_ATTRIBUTE_NAME); private int totalVertexAttributes = 0; private Map registeredVertexAttributes; private int totalEdgeAttributes = 0; private Map registeredEdgeAttributes; private final Set parameters; private String creator = "The JGraphT Library"; private String keywords; private String description; /** * Parameters that affect the behavior of the exporter. */ public enum Parameter { /** * If set the exporter outputs edge weights */ EXPORT_EDGE_WEIGHTS, /** * If set the exporter outputs edge labels. Labels are looked up from the edge attribute * provider. */ EXPORT_EDGE_LABELS, /** * If set the exporter outputs edge types. Edge types are looked up from the type of the * graph. Mixed graphs are not supported. */ EXPORT_EDGE_TYPES, /** * If set the exporter outputs the metadata information. This is true by default. */ EXPORT_META, } /** * Denotes the category of a GEXF-Attribute. */ public enum AttributeCategory { NODE("node"), EDGE("edge"); private String name; private AttributeCategory(String name) { this.name = name; } /** * Get a string representation of the attribute category * * @return the string representation of the attribute category */ public String toString() { return name; } } /** * Constructs a new exporter with integer id providers for the vertices and the edges. */ public GEXFExporter() { this(new IntegerIdProvider(0), new IntegerIdProvider(0)); } /** * Constructs a new exporter. * * @param vertexIdProvider for generating vertex identifiers. Must not be null. * @param edgeIdProvider for generating edge identifiers. Must not be null. */ public GEXFExporter(Function vertexIdProvider, Function edgeIdProvider) { super(vertexIdProvider); this.edgeIdProvider = Optional.of(edgeIdProvider); this.registeredVertexAttributes = new LinkedHashMap<>(); this.registeredEdgeAttributes = new LinkedHashMap<>(); this.parameters = new HashSet<>(); // enable meta by default this.setParameter(Parameter.EXPORT_META, true); } /** * Return if a particular parameter of the exporter is enabled * * @param p the parameter * @return {@code true} if the parameter is set, {@code false} otherwise */ public boolean isParameter(Parameter p) { return parameters.contains(p); } /** * Set the value of a parameter of the exporter * * @param p the parameter * @param value the value to set */ public void setParameter(Parameter p, boolean value) { if (value) { parameters.add(p); } else { parameters.remove(p); } } /** * Register a GEXF Attribute * * @param name the attribute name * @param category the attribute category * @param type the attribute type */ public void registerAttribute(String name, AttributeCategory category, GEXFAttributeType type) { registerAttribute(name, category, type, null); } /** * Register a GEXF Attribute * * @param name the attribute name * @param category the attribute category * @param type the attribute type * @param defaultValue default value */ public void registerAttribute( String name, AttributeCategory category, GEXFAttributeType type, String defaultValue) { registerAttribute(name, category, type, null, null); } /** * Register a GEXF Attribute * * @param name the attribute name * @param category the attribute category * @param type the attribute type * @param defaultValue default value * @param options the possible options */ public void registerAttribute( String name, AttributeCategory category, GEXFAttributeType type, String defaultValue, String options) { if (name == null) { throw new IllegalArgumentException("Attribute name cannot be null"); } if (category == null) { throw new IllegalArgumentException("Attribute category must be one of node or edge"); } if (category.equals(AttributeCategory.NODE)) { if (VERTEX_RESERVED_ATTRIBUTES.contains(name.toLowerCase())) { throw new IllegalArgumentException("Reserved vertex attribute name"); } registeredVertexAttributes .put( name, new AttributeDetails( String.valueOf(totalVertexAttributes++), type, defaultValue, options)); } else if (category.equals(AttributeCategory.EDGE)) { if (EDGE_RESERVED_ATTRIBUTES.contains(name.toLowerCase())) { throw new IllegalArgumentException("Reserved edge attribute name"); } registeredEdgeAttributes .put( name, new AttributeDetails( String.valueOf(totalEdgeAttributes++), type, defaultValue, options)); } } /** * Unregister a GraphML-Attribute * * @param name the attribute name * @param category the attribute category */ public void unregisterAttribute(String name, AttributeCategory category) { if (name == null) { throw new IllegalArgumentException("Attribute name cannot be null"); } if (category == null) { throw new IllegalArgumentException("Attribute category must be one of node or edge"); } if (category.equals(AttributeCategory.NODE)) { if (VERTEX_RESERVED_ATTRIBUTES.contains(name.toLowerCase())) { throw new IllegalArgumentException("Reserved vertex attribute name"); } registeredVertexAttributes.remove(name); } else if (category.equals(AttributeCategory.EDGE)) { if (EDGE_RESERVED_ATTRIBUTES.contains(name.toLowerCase())) { throw new IllegalArgumentException("Reserved edge attribute name"); } registeredEdgeAttributes.remove(name); } } /** * Get the creator for the meta field. * * @return the creator for the meta field */ public String getCreator() { return creator; } /** * Set the creator for the meta field. * * @param creator the creator for the meta field */ public void setCreator(String creator) { this.creator = creator; } /** * Get the keywords for the meta field. * * @return the keywords for the meta field */ public String getKeywords() { return keywords; } /** * Set the keywords for the meta field. * * @param keywords the keywords for the meta field */ public void setKeywords(String keywords) { this.keywords = keywords; } /** * Get the description for the meta field. * * @return the description for the meta field */ public String getDescription() { return description; } /** * Set the description for the meta field. * * @param description the description for the meta field */ public void setDescription(String description) { this.description = description; } /** * Exports a graph in GraphML format. * * @param g the graph * @param writer the writer to export the graph * @throws ExportException in case any error occurs during export */ @Override public void exportGraph(Graph g, Writer writer) { try { // Prepare an XML file to receive the data SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); TransformerHandler handler = factory.newTransformerHandler(); handler.getTransformer().setOutputProperty(OutputKeys.ENCODING, "UTF-8"); handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes"); handler.setResult(new StreamResult(new PrintWriter(writer))); // export handler.startDocument(); writeHeader(handler); writeMeta(handler); writeGraphStart(handler, g); writeVertexAttributes(handler); writeEdgeAttributes(handler); writeVertices(handler, g); writeEdges(handler, g); writeGraphEnd(handler); writeFooter(handler); handler.endDocument(); // flush writer.flush(); } catch (Exception e) { throw new ExportException("Failed to export as GEFX", e); } } private void writeHeader(TransformerHandler handler) throws SAXException { handler.startPrefixMapping("xsi", "http://www.w3.org/2001/XMLSchema-instance"); handler.endPrefixMapping("xsi"); AttributesImpl attr = new AttributesImpl(); attr .addAttribute( "", "", "xsi:schemaLocation", "CDATA", "http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd"); attr.addAttribute("", "", "version", "CDATA", "1.2"); handler.startElement("http://www.gexf.net/1.2draft", "", "gexf", attr); } private void writeMeta(TransformerHandler handler) throws SAXException { boolean exportMeta = parameters.contains(Parameter.EXPORT_META); if (!exportMeta) { return; } if (creator == null && description == null && keywords == null) { return; } handler.startElement("", "", "meta", null); if (creator != null) { handler.startElement("", "", "creator", null); handler.characters(creator.toCharArray(), 0, creator.length()); handler.endElement("", "", "creator"); } if (description != null) { handler.startElement("", "", "description", null); handler.characters(description.toCharArray(), 0, description.length()); handler.endElement("", "", "description"); } if (keywords != null) { handler.startElement("", "", "keywords", null); handler.characters(keywords.toCharArray(), 0, keywords.length()); handler.endElement("", "", "keywords"); } handler.endElement("", "", "meta"); } private void writeGraphStart(TransformerHandler handler, Graph g) throws SAXException { AttributesImpl attr = new AttributesImpl(); attr .addAttribute( "", "", "defaultedgetype", "CDATA", g.getType().isDirected() ? "directed" : "undirected"); handler.startElement("", "", "graph", attr); } private void writeGraphEnd(TransformerHandler handler) throws SAXException { handler.endElement("", "", "graph"); } private void writeFooter(TransformerHandler handler) throws SAXException { handler.endElement("", "", "gexf"); } private void writeVertexAttributes(TransformerHandler handler) throws SAXException { if (registeredVertexAttributes.isEmpty()) { return; } AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "class", "CDATA", "node"); handler.startElement("", "", "attributes", attr); for (Entry e : registeredVertexAttributes.entrySet()) { writeAttribute(handler, e.getKey(), e.getValue()); } handler.endElement("", "", "attributes"); } private void writeEdgeAttributes(TransformerHandler handler) throws SAXException { if (registeredEdgeAttributes.isEmpty()) { return; } AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "class", "CDATA", "edge"); handler.startElement("", "", "attributes", attr); for (Entry e : registeredEdgeAttributes.entrySet()) { writeAttribute(handler, e.getKey(), e.getValue()); } handler.endElement("", "", "attributes"); } private void writeAttribute(TransformerHandler handler, String name, AttributeDetails details) throws SAXException { AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "id", "CDATA", details.key); attr.addAttribute("", "", "title", "CDATA", name); attr.addAttribute("", "", "type", "CDATA", details.type.toString()); handler.startElement("", "", "attribute", attr); if (details.defaultValue != null) { handler.startElement("", "", "default", null); handler .characters(details.defaultValue.toCharArray(), 0, details.defaultValue.length()); handler.endElement("", "", "default"); } if (details.options != null) { handler.startElement("", "", "options", null); handler.characters(details.options.toCharArray(), 0, details.options.length()); handler.endElement("", "", "options"); } handler.endElement("", "", "attribute"); } private void writeVertexAttributeValues(TransformerHandler handler, V v) throws SAXException { Map vertexAttributes = getVertexAttributes(v).orElse(Collections.emptyMap()); if (vertexAttributes.isEmpty()) { return; } handler.startElement("", "", "attvalues", null); // check all registered for (Entry entry : registeredVertexAttributes.entrySet()) { AttributeDetails details = entry.getValue(); String name = entry.getKey(); String defaultValue = details.defaultValue; if (vertexAttributes.containsKey(name)) { Attribute attribute = vertexAttributes.get(name); String value = attribute.getValue(); if (defaultValue == null || !defaultValue.equals(value)) { if (value != null) { writeAttributeValue(handler, details.key, value); } } } } handler.endElement("", "", "attvalues"); } private void writeEdgeAttributeValues(TransformerHandler handler, E e) throws SAXException { Map edgeAttributes = getEdgeAttributes(e).orElse(Collections.emptyMap()); if (edgeAttributes.isEmpty()) { return; } handler.startElement("", "", "attvalues", null); // check all registered for (Entry entry : registeredEdgeAttributes.entrySet()) { AttributeDetails details = entry.getValue(); String name = entry.getKey(); String defaultValue = details.defaultValue; if (edgeAttributes.containsKey(name)) { Attribute attribute = edgeAttributes.get(name); String value = attribute.getValue(); if (defaultValue == null || !defaultValue.equals(value)) { if (value != null) { writeAttributeValue(handler, details.key, value); } } } } handler.endElement("", "", "attvalues"); } private void writeAttributeValue(TransformerHandler handler, String key, String value) throws SAXException { AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "for", "CDATA", key); attr.addAttribute("", "", "value", "CDATA", value); handler.startElement("", "", "attvalue", attr); handler.endElement("", "", "attvalue"); } private void writeVertices(TransformerHandler handler, Graph g) throws SAXException { handler.startElement("", "", "nodes", null); for (V v : g.vertexSet()) { AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "id", "CDATA", getVertexId(v)); attr .addAttribute( "", "", LABEL_ATTRIBUTE_NAME, "CDATA", getVertexAttribute(v, LABEL_ATTRIBUTE_NAME) .map(Attribute::getValue).orElse(getVertexId(v))); handler.startElement("", "", "node", attr); writeVertexAttributeValues(handler, v); handler.endElement("", "", "node"); } handler.endElement("", "", "nodes"); } private void writeEdges(TransformerHandler handler, Graph g) throws SAXException { boolean exportEdgeWeights = parameters.contains(Parameter.EXPORT_EDGE_WEIGHTS); boolean exportEdgeTypes = parameters.contains(Parameter.EXPORT_EDGE_TYPES); boolean exportEdgeLabels = parameters.contains(Parameter.EXPORT_EDGE_LABELS); boolean isGraphDirected = g.getType().isDirected(); handler.startElement("", "", "edges", null); for (E e : g.edgeSet()) { AttributesImpl attr = new AttributesImpl(); attr .addAttribute( "", "", "id", "CDATA", getEdgeId(e) .orElseThrow( () -> new IllegalArgumentException( "Missing or failing edge id provider."))); attr.addAttribute("", "", "source", "CDATA", getVertexId(g.getEdgeSource(e))); attr.addAttribute("", "", "target", "CDATA", getVertexId(g.getEdgeTarget(e))); if (exportEdgeTypes) { attr .addAttribute( "", "", TYPE_ATTRIBUTE_NAME, "CDATA", isGraphDirected ? "directed" : "undirected"); } if (exportEdgeWeights) { attr .addAttribute( "", "", WEIGHT_ATTRIBUTE_NAME, "CDATA", String.valueOf(g.getEdgeWeight(e))); } if (exportEdgeLabels) { getEdgeAttribute(e, LABEL_ATTRIBUTE_NAME).ifPresent(v -> { attr.addAttribute("", "", LABEL_ATTRIBUTE_NAME, "CDATA", v.getValue()); }); } handler.startElement("", "", "edge", attr); writeEdgeAttributeValues(handler, e); handler.endElement("", "", "edge"); } handler.endElement("", "", "edges"); } private class AttributeDetails { public String key; public GEXFAttributeType type; public String defaultValue; public String options; public AttributeDetails( String key, GEXFAttributeType type, String defaultValue, String options) { this.key = key; this.type = type; this.defaultValue = defaultValue; this.options = options; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy