Java tutorial
/** * Copyright 2009 Humboldt-Universitt zu Berlin. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * */ package org.corpus_tools.salt.util; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.CodeSource; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import org.corpus_tools.salt.Beta; import org.corpus_tools.salt.SaltFactory; import org.corpus_tools.salt.common.SDocument; import org.corpus_tools.salt.common.SDominanceRelation; import org.corpus_tools.salt.common.SPointingRelation; import org.corpus_tools.salt.common.SSpan; import org.corpus_tools.salt.common.SSpanningRelation; import org.corpus_tools.salt.common.SStructure; import org.corpus_tools.salt.common.SToken; import org.corpus_tools.salt.core.GraphTraverseHandler; import org.corpus_tools.salt.core.SAnnotation; import org.corpus_tools.salt.core.SGraph.GRAPH_TRAVERSE_TYPE; import org.corpus_tools.salt.core.SNode; import org.corpus_tools.salt.core.SRelation; import org.corpus_tools.salt.exceptions.SaltException; import org.corpus_tools.salt.exceptions.SaltParameterException; import org.corpus_tools.salt.exceptions.SaltResourceException; import org.eclipse.emf.common.util.URI; import org.json.JSONException; import org.json.JSONWriter; import com.google.common.io.ByteStreams; /** * <p> * This class provides a possibility to create a html file, which visualizes a * salt graph, created from an [SDocument](\ref * org.corpus_tools.salt.common.SDocument) or from an <a href= * "http://download.eclipse.org/modeling/emf/emf/javadoc/2.4.3/org/eclipse/emf/common/util/URI.html"> * org.eclipse.emf.common.util.URI</a> of a salt file, using the vis.js library * from <a href="http://visjs.org"> visjs.org</a>. * * * Also it can be used to get both nodes and relations of a salt document in * JSON format. Note, if no export filter used, all nodes and all relations but * textual relations will be visualized. * </p> * * <p> * A simple way to use this class for writing the html file is shown in the * following example code. * </p> * * {@code * * String inputSaltFile = "path_to_your_salt_file"; * * String outputFolder = "path_to_your_output_folder"; * * URI uri = URI.createFileURI(inputSaltFile); * * VisJsVisualizer visJsVisualizer = new VisJsVisualizer(uri); * * try { * * URI outputFileUri = URI.createFileURI(outputFolder); * * visJsVisualizer.visualize(outputFileUri); * * } catch (IOException | XMLStreamException e) { * * e.printStackTrace(); * * } * * } * * <p> * The next listing shows how to get the nodes and the relations of an input * salt file in JSON format by use of this class. For simplicity, the created * JSON objects will be written to the standard output. {@code * * URI uri = URI.createFileURI("path_to_the_input_salt_file"); * * OutputStream nodeStream = Sytem.out; * OutputStream edgeStream = System.out; * * VisJsVisualizer VisJsVisualizer = new VisJsVisualizer(uri); * * VisJsVisualizer.setNodeWriter(nodeStream); * * VisJsVisualizer.setEdgeWriter(edgeStream); * * VisJsVisualizer.buildJSON(); * * * try { * * nodeStream.write('\n'); nodeStream.flush(); * * edgeStream.flush(); * * } catch (IOException e) { * * e.printStackTrace(); * * } } * * @author irina */ @Beta public class VisJsVisualizer implements GraphTraverseHandler { private long maxHeight; private int currHeight; private int maxLevel; private int currHeightFromToken; private SDocument doc; private String docId; public BufferedWriter nodeWriter; public BufferedWriter edgeWriter; private JSONWriter jsonWriterNodes; private JSONWriter jsonWriterEdges; // HTML tags private static final String TAG_HTML = "html"; private static final String TAG_HEAD = "head"; private static final String TAG_BODY = "body"; private static final String TAG_TITLE = "title"; private static final String TAG_P = "p"; private static final String TAG_DIV = "div"; private static final String TAG_SCRIPT = "script"; private static final String TAG_STYLE = "style"; private static final String TAG_LINK = "link"; private static final String TAG_H2 = "h2"; private static final String TAG_INPUT = "input"; // HTML attributes private static final String ATT_TYPE = "type"; private static final String ATT_ID = "id"; private static final String ATT_VALUE = "value"; private static final String ATT_SRC = "src"; private static final String ATT_HREF = "href"; private static final String ATT_REL = "rel"; private static final String ATT_STYLE = "style"; private static final String ATT_LANG = "language"; private static final String ATT_CLASS = "class"; private static final String TEXT_STYLE = "width:700px; font-size:14px; text-align: justify;"; private static final String TRAV_MODE_CALC_LEVEL = "calcLevel"; private static final String TRAV_MODE_READ_NODES = "readNodes"; private final HashSet<SNode> readSpanNodes; private final HashSet<SNode> readStructNodes; private final HashSet<SRelation> readRelations; private final List<SNode> roots; private Map<SNode, Integer> rootToMinLevel; /* * identifies, which kinds of nodes (unless token nodes) the graph possesses * 3 = spanning nodes and structure nodes 2 = structure nodes 1 = spanning * nodes 0 = neither spanning nodes nor structure nodes */ private int nGroupsId = 0; // JSON output private static final String JSON_ID = "id"; private static final String JSON_LABEL = "label"; private static final String JSON_COLOR = "color"; private static final String JSON_COLOR_BACKGROUND = "background"; private static final String JSON_COLOR_BORDER = "border"; private static final String JSON_X = "x"; private static final String JSON_LEVEL = "level"; private static final String JSON_GROUP = "group"; private static final String JSON_PHYSICS = "physics"; private static final String JSON_SMOOTH = "smooth"; private static final String JSON_TYPE = "type"; private static final String JSON_ROUNDNESS = "roundness"; private static final String JSON_WIDTH = "width"; private static final String JSON_EDGE_FROM = "from"; private static final String JSON_EDGE_TO = "to"; private static final String JSON_BORDER_WIDTH = "borderWidth"; private static final String JSON_FONT = "font"; private static final String JSON_FONT_SIZE = "size"; private int xPosition = 0; private int nTokens = 0; private static final int NODE_DIST = 150; private static final String TOK_COLOR_VALUE = "#ccff99"; private static final String SPAN_COLOR_VALUE = "#dbdcff"; private static final String STRUCTURE_COLOR_VALUE = "#ffff7d"; private static final String TOK_BORDER_COLOR_VALUE = "#b7e589"; private static final String SPAN_BORDER_COLOR_VALUE = "#c5c6e5"; private static final String STRUCTURE_BORDER_COLOR_VALUE = "#e5e570"; private static final String HIGHLIGHTING_BORDER_WIDTH = "5"; private static final String EDGE_WIDTH = "2"; private static final String JSON_EDGE_TYPE_VALUE = "curvedCW"; private static final String JSON_ROUNDNESS_VALUE = "0.95"; private static final String JSON_FONT_SIZE_VALUE = "18"; private static final String NEWLINE = System.lineSeparator(); private final ExportFilter exportFilter; private final StyleImporter styleImporter; private boolean writeNodeImmediately = false; public static final String CSS_FOLDER_OUT = "css"; public static final String IMG_FOLDER_OUT = CSS_FOLDER_OUT + System.getProperty("file.separator") + "img" + System.getProperty("file.separator") + "network"; public static final String JS_FOLDER_OUT = "js"; public static final String CSS_FILE = "vis.min.css"; public static final String JS_FILE = "vis.min.js"; public static final String JQUERY_FILE = "jquery.js"; public static final String HTML_FILE = "saltVisJs.html"; private final File tmpFile; private final static String JQUERY_SRC = JS_FOLDER_OUT + System.getProperty("file.separator") + JQUERY_FILE; private final static String VIS_JS_SRC = JS_FOLDER_OUT + System.getProperty("file.separator") + JS_FILE; private final static String VIS_CSS_SRC = CSS_FOLDER_OUT + System.getProperty("file.separator") + CSS_FILE; private static final String RESOURCE_FOLDER = System.getProperty("file.separator") + "visjs"; private static final String RESOURCE_FOLDER_IMG_NETWORK = "visjs" + System.getProperty("file.separator") + "img" + System.getProperty("file.separator") + "network"; private HashMap<String, Integer> spanClasses; private int maxSpanOffset = -1; private int nNodes = 0; private boolean withPhysics = false; /** * Creates a new VisJsVisualizer instance for specified salt document. * * @param doc * an [SDocument](\ref org.corpus_tools.salt.common.SDocument) to * be visualized * * @throws IOException * if creation of tmp file failed * * @throws SaltParameterException * if the doc is null */ public VisJsVisualizer(SDocument doc) throws IOException { this(doc, null, null); } /** * Creates a new VisJsVisualizer instance with specified export filter for * specified salt document. * * @param doc * an [SDocument](\ref org.corpus_tools.salt.common.SDocument) to * be visualized * @param exportFilter * an {@link ExportFilter} to include or exclude nodes and/or * relations explicitly. If null, all nodes and relations will be * visualized. * @param styleImporter * a {@link StyleImporter} to highlight nodes. If null, no nodes * will be highlighted. * * @throws IOException * if creation of tmp file failed * @throws SaltParameterException * if doc is null */ public VisJsVisualizer(SDocument doc, ExportFilter exportFilter, StyleImporter styleImporter) throws IOException { if (doc == null) throw new SaltParameterException("doc", "VisJsVisualizer", this.getClass()); this.doc = doc; docId = doc.getId(); roots = doc.getDocumentGraph().getRoots(); rootToMinLevel = new HashMap<SNode, Integer>(); readSpanNodes = new HashSet<SNode>(); readStructNodes = new HashSet<SNode>(); readRelations = new HashSet<SRelation>(); tmpFile = File.createTempFile("tmp_salt", "vis"); this.exportFilter = exportFilter; this.styleImporter = styleImporter; spanClasses = new HashMap<String, Integer>(); // delete tmp file tmpFile.deleteOnExit(); } /** * Creates a new VisJsVisualizer instance for a salt file specified by the * uri. * * @param inputFileUri * a hierarchical <a href= * "http://download.eclipse.org/modeling/emf/emf/javadoc/2.4.3/org/eclipse/emf/common/util/URI.html"> * org.eclipse.emf.common.util.URI</a> of a salt file to be * visualized. The constructor will create a new [SDocument](\ref * org.corpus_tools.salt.common.SDocument) of this. * * @throws IOException * if creation of tmp file failed * @throws SaltParameterException * - if the inputFileUri is null */ public VisJsVisualizer(URI inputFileUri) throws IOException { this(inputFileUri, null, null); } /** * Creates a new VisJsVisualizer instance with specified export filter for a * salt file specified by the uri. * * @param inputFileUri * a hierarchical <a href= * "http://download.eclipse.org/modeling/emf/emf/javadoc/2.4.3/org/eclipse/emf/common/util/URI.html"> * org.eclipse.emf.common.util.URI</a> of a salt file, which has * to be visualized. The constructor will create a new * [SDocument](\ref org.corpus_tools.salt.common.SDocument) of * this. * @param exportFilter * an {@link ExportFilter} to include or exclude nodes and/or * relations explicitly. If null, all nodes and relations will be * visualized. * @param styleImporter * a {@link StyleImporter} to highlight nodes. If null, no nodes * will be highlighted. * @throws IOException * if creation of tmp file failed * * @throws SaltParameterException * if the inputFileUri is null * @throws SaltResourceException * if a problem occurred while loading salt project from the * inputFileUri */ public VisJsVisualizer(URI inputFileUri, ExportFilter exportFilter, StyleImporter styleImporter) throws IOException { if (inputFileUri == null) throw new SaltParameterException("inputUri", "VisJsVisualizer", this.getClass()); try { this.doc = SaltFactory.createSDocument(); doc.loadDocumentGraph(inputFileUri); docId = doc.getId(); } catch (SaltResourceException e) { throw new SaltResourceException( "A problem occurred while loading salt project from '" + inputFileUri + "'.", e); } roots = doc.getDocumentGraph().getRoots(); rootToMinLevel = new HashMap<SNode, Integer>(); readSpanNodes = new HashSet<SNode>(); readStructNodes = new HashSet<SNode>(); readRelations = new HashSet<SRelation>(); tmpFile = File.createTempFile("tmp_salt", "vis"); this.exportFilter = exportFilter; this.styleImporter = styleImporter; spanClasses = new HashMap<String, Integer>(); // delete tmp file tmpFile.deleteOnExit(); } /** * <p> * This method writes the html document, which visualizes the Salt document, * specified by constructor. The output folder structure will be created, if * not yet exists. The output html file as well as the auxiliary files will * be written. * </p> * * The whole output structure will look like following: </br> * * * ![](./images/file_tree.png) * * @param outputFolderUri * a hierarchical <a href= * "http://download.eclipse.org/modeling/emf/emf/javadoc/2.4.3/org/eclipse/emf/common/util/URI.html"> * org.eclipse.emf.common.util.URI</a> that specifies the output * folder path. Note, that the output folder have not necessarily * to be existing. * * @throws SaltParameterException * if the outputFolderUri is null * @throws SaltResourceException * if the output auxiliary files cannot have been created * @throws SaltException * if the output folders cannot have been created or permission * denied * @throws SaltResourceException * if a problem occurred while copying the auxiliary files * @throws XMLStreamException * if a problem occurred while writing the output html file * @throws IOException * if a problem occurred while writing the output file */ public void visualize(URI outputFolderUri) throws SaltParameterException, SaltResourceException, SaltException, SaltResourceException, IOException, XMLStreamException { try { File outputFolder = createOutputResources(outputFolderUri); writeNodeImmediately = true; try { writeHTML(outputFolder); } catch (SaltParameterException e) { throw new SaltParameterException(e.getMessage()); } catch (SaltException e) { throw new SaltException(e.getMessage()); } writeNodeImmediately = false; } catch (SaltParameterException e) { throw new SaltParameterException("outputFileUri", "writeHTML", this.getClass()); } catch (FileNotFoundException e) { throw new SaltResourceException("The output auxiliary files cannot be created."); } catch (SecurityException e) { throw new SaltException("Either the output folder cannot be created or permission denied."); } catch (IOException e) { throw new SaltResourceException("A problem occurred while copying the vis-js ressource files"); } } private void writeHTML(File outputFolder) throws XMLStreamException, IOException { int nodeDist = 0; int sprLength = 0; double sprConstant = 0.0; try (OutputStream os = new FileOutputStream(new File(outputFolder, HTML_FILE)); FileOutputStream fos = new FileOutputStream(tmpFile)) { XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); XMLStreamWriter xmlWriter = outputFactory.createXMLStreamWriter(os, "UTF-8"); setNodeWriter(os); setEdgeWriter(fos); xmlWriter.writeStartDocument("UTF-8", "1.0"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_HTML); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_HEAD); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_TITLE); xmlWriter.writeCharacters("Salt Document Tree"); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_STYLE); xmlWriter.writeAttribute(ATT_TYPE, "text/css"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeCharacters("body {" + NEWLINE + "font: 10pt sans;" + NEWLINE + "}" + NEWLINE + "#mynetwork {" + NEWLINE + "height: 90%;" + NEWLINE + "width: 90%;" + NEWLINE + "border: 1px solid lightgray; " + NEWLINE + "text-align: center;" + NEWLINE + "}" + NEWLINE + "#loadingBar {" + NEWLINE + "position:absolute;" + NEWLINE + "top:0px;" + NEWLINE + "left:0px;" + NEWLINE + "width: 0px;" + NEWLINE + "height: 0px;" + NEWLINE + "background-color:rgba(200,200,200,0.8);" + NEWLINE + "-webkit-transition: all 0.5s ease;" + NEWLINE + "-moz-transition: all 0.5s ease;" + NEWLINE + "-ms-transition: all 0.5s ease;" + NEWLINE + "-o-transition: all 0.5s ease;" + NEWLINE + "transition: all 0.5s ease;" + NEWLINE + "opacity:1;" + NEWLINE + "}" + NEWLINE + "#wrapper {" + NEWLINE + "position:absolute;" + NEWLINE + "width: 1200px;" + NEWLINE + "height: 90%;" + NEWLINE + "}" + NEWLINE + "#text {" + NEWLINE + "position:absolute;" + NEWLINE + "top:8px;" + NEWLINE + "left:530px;" + NEWLINE + "width:30px;" + NEWLINE + "height:50px;" + NEWLINE + "margin:auto auto auto auto;" + NEWLINE + "font-size:16px;" + NEWLINE + "color: #000000;" + NEWLINE + "}" + NEWLINE + "div.outerBorder {" + NEWLINE + "position:relative;" + NEWLINE + "top:400px;" + NEWLINE + "width:600px;" + NEWLINE + "height:44px;" + NEWLINE + "margin:auto auto auto auto;" + NEWLINE + "border:8px solid rgba(0,0,0,0.1);" + NEWLINE + "background: rgb(252,252,252); /* Old browsers */" + NEWLINE + "background: -moz-linear-gradient(top, rgba(252,252,252,1) 0%, rgba(237,237,237,1) 100%); /* FF3.6+ */" + NEWLINE + "background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(252,252,252,1)), color-stop(100%,rgba(237,237,237,1))); /* Chrome,Safari4+ */" + NEWLINE + "background: -webkit-linear-gradient(top, rgba(252,252,252,1) 0%,rgba(237,237,237,1) 100%); /* Chrome10+,Safari5.1+ */" + NEWLINE + "background: -o-linear-gradient(top, rgba(252,252,252,1) 0%,rgba(237,237,237,1) 100%); /* Opera 11.10+ */" + NEWLINE + "background: -ms-linear-gradient(top, rgba(252,252,252,1) 0%,rgba(237,237,237,1) 100%); /* IE10+ */" + NEWLINE + "background: linear-gradient(to bottom, rgba(252,252,252,1) 0%,rgba(237,237,237,1) 100%); /* W3C */" + NEWLINE + "filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcfcfc', endColorstr='#ededed',GradientType=0 ); /* IE6-9 */" + NEWLINE + "border-radius:72px;" + NEWLINE + "box-shadow: 0px 0px 10px rgba(0,0,0,0.2);" + NEWLINE + "}" + NEWLINE + "#border {" + NEWLINE + "position:absolute;" + NEWLINE + "top:10px;" + NEWLINE + "left:10px;" + NEWLINE + "width:500px;" + NEWLINE + "height:23px;" + NEWLINE + "margin:auto auto auto auto;" + NEWLINE + "box-shadow: 0px 0px 4px rgba(0,0,0,0.2);" + NEWLINE + "border-radius:10px;" + NEWLINE + "}" + NEWLINE + "#bar {" + NEWLINE + "position:absolute;" + NEWLINE + "top:0px;" + NEWLINE + "left:0px;" + NEWLINE + "width:20px;" + NEWLINE + "height:20px;" + NEWLINE + "margin:auto auto auto auto;" + NEWLINE + "border-radius:6px;" + NEWLINE + "border:1px solid rgba(30,30,30,0.05);" + NEWLINE + "background: rgb(0, 173, 246); /* Old browsers */" + NEWLINE + "box-shadow: 2px 0px 4px rgba(0,0,0,0.4);" + NEWLINE + "}" + NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_SCRIPT); xmlWriter.writeAttribute(ATT_SRC, VIS_JS_SRC); xmlWriter.writeAttribute(ATT_TYPE, "text/javascript"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_SCRIPT); xmlWriter.writeAttribute(ATT_SRC, JQUERY_SRC); xmlWriter.writeAttribute(ATT_TYPE, "text/javascript"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEmptyElement(TAG_LINK); xmlWriter.writeAttribute(ATT_HREF, VIS_CSS_SRC); xmlWriter.writeAttribute(ATT_REL, "stylesheet"); xmlWriter.writeAttribute(ATT_TYPE, "text/css"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_SCRIPT); xmlWriter.writeAttribute(ATT_TYPE, "text/javascript"); xmlWriter.writeCharacters(NEWLINE + "function frameSize() {" + NEWLINE + "$(document).ready(function() {" + NEWLINE + "function elementResize() {" + NEWLINE + "var browserWidth = $(window).width()*0.98;" + NEWLINE + "document.getElementById('mynetwork').style.width = browserWidth;" + NEWLINE + "}" + NEWLINE + "elementResize();" + NEWLINE + "$(window).bind(\"resize\", function(){" + NEWLINE + "elementResize();" + NEWLINE + "});" + NEWLINE + "});" + NEWLINE + "}" + NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_SCRIPT); xmlWriter.writeAttribute(ATT_TYPE, "text/javascript"); xmlWriter.writeCharacters(NEWLINE + "function start(){" + NEWLINE + "loadSaltObjectAndDraw();" + NEWLINE + "frameSize();" + NEWLINE + "}" + NEWLINE + "var nodesJson = [];" + NEWLINE + "var edgesJson = [];" + NEWLINE + "var network = null;" + NEWLINE + "function loadSaltObjectAndDraw() {" + NEWLINE + "var nodesJson = " + NEWLINE); xmlWriter.flush(); try { buildJSON(); } catch (SaltParameterException e) { throw new SaltParameterException(e.getMessage()); } catch (SaltException e) { throw new SaltException(e.getMessage()); } if (nNodes < 20) { nodeDist = 120; sprConstant = 1.2; sprLength = 120; } else if (nNodes >= 20 && nNodes < 100) { nodeDist = 150; sprConstant = 1.1; sprLength = 160; } else if (nNodes >= 100 && nNodes < 400) { nodeDist = 180; sprConstant = 0.9; sprLength = 180; } else if (nNodes >= 400 && nNodes < 800) { nodeDist = 200; sprConstant = 0.6; sprLength = 200; } else { nodeDist = 250; sprConstant = 0.3; sprLength = 230; } ; // write nodes as array nodeWriter.flush(); xmlWriter.writeCharacters(";" + NEWLINE); xmlWriter.writeCharacters("var edgesJson = " + NEWLINE); xmlWriter.flush(); // write edges as array to tmp file edgeWriter.flush(); // copy edges from tmp file ByteStreams.copy(new FileInputStream(tmpFile), os); xmlWriter.writeCharacters(";" + NEWLINE); xmlWriter.writeCharacters("var nodeDist =" + nodeDist + ";" + NEWLINE); xmlWriter.writeCharacters("draw(nodesJson, edgesJson, nodeDist);" + NEWLINE + "}" + NEWLINE + "var directionInput = document.getElementById(\"direction\");" + NEWLINE + "function destroy() {" + NEWLINE + "if (network !== null) {" + NEWLINE + "network.destroy();" + NEWLINE + "network = null;" + NEWLINE + "}" + NEWLINE + "}" + NEWLINE + NEWLINE + "function draw(nodesJson, edgesJson, nodeDist) {" + NEWLINE + "destroy();" + NEWLINE + "var connectionCount = [];" + NEWLINE + "var nodes = [];" + NEWLINE + "var edges = [];" + NEWLINE + NEWLINE + "nodes = new vis.DataSet(nodesJson);" + NEWLINE + "edges = new vis.DataSet(edgesJson);" + NEWLINE + "var container = document.getElementById('mynetwork');" + NEWLINE + "var data = {" + NEWLINE + "nodes: nodes," + NEWLINE + "edges: edges" + NEWLINE + "};" + NEWLINE + "var options = {" + NEWLINE + "nodes:{" + NEWLINE + "shape: \"box\"" + NEWLINE + "}," + NEWLINE + "edges: {" + NEWLINE + "smooth: true," + NEWLINE + "arrows: {" + NEWLINE + "to: {" + NEWLINE + "enabled: true" + NEWLINE + "}" + NEWLINE + "}" + NEWLINE + "}," + NEWLINE + "interaction: {" + NEWLINE + "navigationButtons: true," + NEWLINE + "keyboard: true" + NEWLINE + "}," + NEWLINE + "layout: {" + NEWLINE + "hierarchical:{" + NEWLINE + "direction: directionInput.value" + NEWLINE + "}" + NEWLINE + "}," + NEWLINE + "physics: {" + NEWLINE + "hierarchicalRepulsion: {" + NEWLINE + "centralGravity: 0.8," + NEWLINE + "springLength: " + sprLength + "," + NEWLINE + "springConstant: " + sprConstant + "," + NEWLINE + "nodeDistance: nodeDist," + NEWLINE + "damping: 0.04" + NEWLINE + "}," + NEWLINE + "maxVelocity: 50," + NEWLINE + "minVelocity: 1," + NEWLINE + "solver: 'hierarchicalRepulsion'," + NEWLINE + "timestep: 0.5," + NEWLINE + "stabilization: {" + NEWLINE + "iterations: 1000" + NEWLINE + "}" + NEWLINE + "}" + NEWLINE + "}" + NEWLINE + ";" + NEWLINE + "network = new vis.Network(container, data, options);" + NEWLINE); if (withPhysics == true) { xmlWriter.writeCharacters("network.on(\"stabilizationProgress\", function(params) {" + NEWLINE + "var maxWidth = 496;" + NEWLINE + "var minWidth = 20;" + NEWLINE + "var widthFactor = params.iterations/params.total;" + NEWLINE + "var width = Math.max(minWidth,maxWidth * widthFactor);" + NEWLINE + "document.getElementById('loadingBar').style.opacity = 1;" + NEWLINE + "document.getElementById('bar').style.width = width + 'px';" + NEWLINE + "document.getElementById('text').innerHTML = Math.round(widthFactor*100) + '%';" + NEWLINE + "});" + NEWLINE + "network.on(\"stabilizationIterationsDone\", function() {" + NEWLINE + "document.getElementById('text').innerHTML = '100%';" + NEWLINE + "document.getElementById('bar').style.width = '496px';" + NEWLINE + "document.getElementById('loadingBar').style.opacity = 0;" + NEWLINE + "});" + NEWLINE); } xmlWriter.writeCharacters("}" + NEWLINE); // script xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); // head xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_BODY); xmlWriter.writeAttribute("onload", "start();"); xmlWriter.writeCharacters(NEWLINE); if (withPhysics == true) { xmlWriter.writeStartElement(TAG_DIV); xmlWriter.writeAttribute(ATT_ID, "wrapper"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_DIV); xmlWriter.writeAttribute(ATT_ID, "loadingBar"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_DIV); xmlWriter.writeAttribute(ATT_CLASS, "outerBorder"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_DIV); xmlWriter.writeAttribute(ATT_ID, "text"); xmlWriter.writeCharacters("0%"); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_DIV); xmlWriter.writeAttribute(ATT_ID, "border"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_DIV); xmlWriter.writeAttribute(ATT_ID, "bar"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); } xmlWriter.writeStartElement(TAG_H2); xmlWriter.writeCharacters("Dokument-Id: " + docId); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_DIV); xmlWriter.writeAttribute(ATT_STYLE, TEXT_STYLE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement("p"); xmlWriter.writeEmptyElement(TAG_INPUT); xmlWriter.writeAttribute(ATT_TYPE, "button"); xmlWriter.writeAttribute(ATT_ID, "btn-UD"); xmlWriter.writeAttribute(ATT_VALUE, "Up-Down"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEmptyElement(TAG_INPUT); xmlWriter.writeAttribute(ATT_TYPE, "button"); xmlWriter.writeAttribute(ATT_ID, "btn-DU"); xmlWriter.writeAttribute(ATT_VALUE, "Down-Up"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEmptyElement(TAG_INPUT); xmlWriter.writeAttribute(ATT_TYPE, "hidden"); // TODO check the apostrophes xmlWriter.writeAttribute(ATT_ID, "direction"); xmlWriter.writeAttribute(ATT_VALUE, "UD"); xmlWriter.writeCharacters(NEWLINE); // p xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_DIV); xmlWriter.writeAttribute(ATT_ID, "mynetwork"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_P); xmlWriter.writeAttribute(ATT_ID, "selection"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeStartElement(TAG_SCRIPT); xmlWriter.writeAttribute(ATT_LANG, "JavaScript"); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeCharacters("var directionInput = document.getElementById(\"direction\");" + NEWLINE + "var btnUD = document.getElementById(\"btn-UD\");" + NEWLINE + "btnUD.onclick = function() {" + NEWLINE + "directionInput.value = \"UD\";" + NEWLINE + "start();" + NEWLINE + "};" + NEWLINE + "var btnDU = document.getElementById(\"btn-DU\");" + NEWLINE + "btnDU.onclick = function() {" + NEWLINE + "directionInput.value = \"DU\";" + NEWLINE + "start();" + NEWLINE + "};" + NEWLINE); xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); // div wrapper if (withPhysics == true) { xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); } // body xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); // html xmlWriter.writeEndElement(); xmlWriter.writeCharacters(NEWLINE); xmlWriter.writeEndDocument(); xmlWriter.flush(); xmlWriter.close(); nodeWriter.close(); edgeWriter.close(); } } /* * Organizes the output folder structure and invokes the method for copying * of auxiliary files. */ private File createOutputResources(URI outputFileUri) throws SaltParameterException, SecurityException, FileNotFoundException, IOException { File outputFolder = null; if (outputFileUri == null) { throw new SaltParameterException("Cannot store salt-vis, because the passed output uri is empty. "); } outputFolder = new File(outputFileUri.path()); if (!outputFolder.exists()) { if (!outputFolder.mkdirs()) { throw new SaltException("Can't create folder " + outputFolder.getAbsolutePath()); } } File cssFolderOut = new File(outputFolder, CSS_FOLDER_OUT); if (!cssFolderOut.exists()) { if (!cssFolderOut.mkdir()) { throw new SaltException("Can't create folder " + cssFolderOut.getAbsolutePath()); } } File jsFolderOut = new File(outputFolder, JS_FOLDER_OUT); if (!jsFolderOut.exists()) { if (!jsFolderOut.mkdir()) { throw new SaltException("Can't create folder " + jsFolderOut.getAbsolutePath()); } } File imgFolderOut = new File(outputFolder, IMG_FOLDER_OUT); if (!imgFolderOut.exists()) { if (!imgFolderOut.mkdirs()) { throw new SaltException("Can't create folder " + imgFolderOut.getAbsolutePath()); } } copyResourceFile( getClass().getResourceAsStream(RESOURCE_FOLDER + System.getProperty("file.separator") + CSS_FILE), outputFolder.getPath(), CSS_FOLDER_OUT, CSS_FILE); copyResourceFile( getClass().getResourceAsStream(RESOURCE_FOLDER + System.getProperty("file.separator") + JS_FILE), outputFolder.getPath(), JS_FOLDER_OUT, JS_FILE); copyResourceFile( getClass() .getResourceAsStream(RESOURCE_FOLDER + System.getProperty("file.separator") + JQUERY_FILE), outputFolder.getPath(), JS_FOLDER_OUT, JQUERY_FILE); ClassLoader classLoader = getClass().getClassLoader(); CodeSource srcCode = VisJsVisualizer.class.getProtectionDomain().getCodeSource(); URL codeSourceUrl = srcCode.getLocation(); File codeSourseFile = new File(codeSourceUrl.getPath()); if (codeSourseFile.isDirectory()) { File imgFolder = new File(classLoader.getResource(RESOURCE_FOLDER_IMG_NETWORK).getFile()); File[] imgFiles = imgFolder.listFiles(); if (imgFiles != null) { for (File imgFile : imgFiles) { InputStream inputStream = getClass() .getResourceAsStream(System.getProperty("file.separator") + RESOURCE_FOLDER_IMG_NETWORK + System.getProperty("file.separator") + imgFile.getName()); copyResourceFile(inputStream, outputFolder.getPath(), IMG_FOLDER_OUT, imgFile.getName()); } } } else if (codeSourseFile.getName().endsWith("jar")) { JarFile jarFile = new JarFile(codeSourseFile); Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().startsWith(RESOURCE_FOLDER_IMG_NETWORK) && !entry.isDirectory()) { copyResourceFile(jarFile.getInputStream(entry), outputFolder.getPath(), IMG_FOLDER_OUT, entry.getName().replaceFirst(RESOURCE_FOLDER_IMG_NETWORK, "")); } } jarFile.close(); } return outputFolder; } private void copyResourceFile(InputStream inputStream, String outputFolder, String outSubFolder, String outFile) throws IOException { File outFileObject; if (outSubFolder != null) { outFileObject = new File(outputFolder + System.getProperty("file.separator") + outSubFolder + System.getProperty("file.separator") + outFile); } else { outFileObject = new File(outputFolder + System.getProperty("file.separator") + outFile); } try (FileOutputStream fileOutStream = new FileOutputStream(outFileObject)) { int bufferSize = 32 * 1024; byte[] bytes = new byte[bufferSize]; int readBytes = 0; while ((readBytes = inputStream.read(bytes)) != -1) { fileOutStream.write(bytes, 0, readBytes); } fileOutStream.flush(); fileOutStream.close(); inputStream.close(); } } /** * Creates a new buffered writer with specified output stream. It will * contain the nodes in JSON format after invoking of the * {@link #buildJSON()} method. * * @param os * OutputStream associated to the node writer. */ public void setNodeWriter(OutputStream os) { this.nodeWriter = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8)); this.jsonWriterNodes = new JSONWriter(nodeWriter); } /** * Creates a new buffered writer with specified output stream. It will * contain the edges in JSON format after invoking of the * {@link #buildJSON()} method. * * @param os * OutputStream associated to the edge writer */ public void setEdgeWriter(OutputStream os) { this.edgeWriter = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8)); this.jsonWriterEdges = new JSONWriter(edgeWriter); } /** * * By invoking of this method the graph of the salt document specified by * the constructor will be traversed. Both the {@link #nodeWriter} and the * {@link #edgeWriter} write nodes and relations of this graph respective to * the associated output streams. * * @throws SaltException * if a problem occurred while building JSON objects * @throws SaltParameterException * if the node writer and/or the edge writer not set */ public void buildJSON() throws SaltException, SaltParameterException { maxLevel = getMaxLevel(doc); doc.getDocumentGraph().sortTokenByText(); List<SToken> sTokens = doc.getDocumentGraph().getTokens(); if (nodeWriter == null || jsonWriterNodes == null) { throw new SaltParameterException( "A problem occurred while building JSON objects. Probably the node writer is not set."); } if (edgeWriter == null || jsonWriterEdges == null) { throw new SaltParameterException( "A problem occurred while building JSON objects. Probably the edge writer is not set."); } try { // create node array jsonWriterNodes.array(); // token must always be output for (SToken token : sTokens) { writeJsonNode(token, maxLevel); nNodes++; } nTokens = sTokens.size(); // create edge array jsonWriterEdges.array(); // traverse the document tree in order to write remained nodes and // edges doc.getDocumentGraph().traverse(doc.getDocumentGraph().getRoots(), GRAPH_TRAVERSE_TYPE.TOP_DOWN_DEPTH_FIRST, TRAV_MODE_READ_NODES, this); // close node array jsonWriterNodes.endArray(); // close edge array jsonWriterEdges.endArray(); nodeWriter.flush(); edgeWriter.flush(); } catch (JSONException e) { throw new SaltException("A problem occurred while building JSON objects."); } catch (IOException e) { throw new SaltException("A problem occurred while building JSON objects."); } } private static List<Map.Entry<String, String>> sortAnnotations(Set<SAnnotation> sAnnotations) { Map<String, String> annotationMap = new HashMap<String, String>(); for (SAnnotation sAnnotation : sAnnotations) { annotationMap.put(sAnnotation.getName(), sAnnotation.getValue().toString()); } List<Map.Entry<String, String>> sortedAnnotations = sortByKey(annotationMap); return sortedAnnotations; } private static <K, V> List<Map.Entry<K, V>> sortByKey(Map<K, V> map) { List<Map.Entry<K, V>> entries = new ArrayList<Map.Entry<K, V>>(map.size()); // don't use addAll with a view of the entries: // http://findbugs.sourceforge.net/bugDescriptions.html#DMI_ENTRY_SETS_MAY_REUSE_ENTRY_OBJECTS for (Map.Entry<K, V> e : map.entrySet()) { entries.add(e); } Comparator<Map.Entry<K, V>> comparator = new Comparator<Map.Entry<K, V>>() { public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { return e1.getKey().toString().compareToIgnoreCase(e2.getKey().toString()); } }; // sort key values lexicographically Collections.sort(entries, comparator); return entries; } private void writeJsonNode(SNode node, long levelValue) throws SaltParameterException, IOException { String highlightingColor = null; if (styleImporter != null) { highlightingColor = styleImporter.setHighlightingColor(node); } String idValue = node.getPath().fragment(); String idLabel = "id=" + idValue; StringBuilder allLabels = new StringBuilder(idLabel); // node object jsonWriterNodes.object(); jsonWriterNodes.key(JSON_ID); jsonWriterNodes.value(idValue); jsonWriterNodes.key(JSON_LABEL); Set<SAnnotation> sAnnotations = node.getAnnotations(); // sort annotation keys lexicographically List<Map.Entry<String, String>> sortedAnnotations = sortAnnotations(sAnnotations); // add all annotation key-value-pairs for (Map.Entry<String, String> annotation : sortedAnnotations) { allLabels.append(NEWLINE); allLabels.append(annotation.getKey()).append("=").append(annotation.getValue()); } // add token text if (node instanceof SToken) { String text = doc.getDocumentGraph().getText(node); if (text != null && !text.isEmpty()) { allLabels.append(NEWLINE).append(NEWLINE).append(text); } } jsonWriterNodes.value(allLabels.toString()); String nodeColorValue; String nodeColorBorder; if (node instanceof SToken) { nodeColorValue = TOK_COLOR_VALUE; nodeColorBorder = TOK_BORDER_COLOR_VALUE; jsonWriterNodes.key(JSON_X); jsonWriterNodes.value((xPosition++) * NODE_DIST); // in order to keep the relative order to each other, tokens are not // part of physics jsonWriterNodes.key(JSON_PHYSICS); jsonWriterNodes.value("false"); } else if (node instanceof SSpan) { nodeColorValue = SPAN_COLOR_VALUE; nodeColorBorder = SPAN_BORDER_COLOR_VALUE; if (nGroupsId == 3) { jsonWriterNodes.key(JSON_GROUP); jsonWriterNodes.value("1"); } else { jsonWriterNodes.key(JSON_GROUP); jsonWriterNodes.value("0"); } // initial x-value in center jsonWriterNodes.key(JSON_X); jsonWriterNodes.value((nTokens / 2) * NODE_DIST); } else if (node instanceof SStructure) { nodeColorValue = STRUCTURE_COLOR_VALUE; nodeColorBorder = STRUCTURE_BORDER_COLOR_VALUE; jsonWriterNodes.key(JSON_GROUP); jsonWriterNodes.value("0"); // initial x-value in center jsonWriterNodes.key(JSON_X); jsonWriterNodes.value((nTokens / 2) * NODE_DIST); } else { throw new SaltParameterException(node.getId(), "writeJsonNode", this.getClass()); } jsonWriterNodes.key(JSON_COLOR); jsonWriterNodes.object(); jsonWriterNodes.key(JSON_COLOR_BACKGROUND); jsonWriterNodes.value(nodeColorValue); if (highlightingColor != null) { jsonWriterNodes.key(JSON_COLOR_BORDER); jsonWriterNodes.value(highlightingColor); jsonWriterNodes.endObject(); // end color jsonWriterNodes.key(JSON_BORDER_WIDTH); jsonWriterNodes.value(HIGHLIGHTING_BORDER_WIDTH); } else { jsonWriterNodes.key(JSON_COLOR_BORDER); jsonWriterNodes.value(nodeColorBorder); jsonWriterNodes.endObject(); // end color } jsonWriterNodes.key(JSON_LEVEL); jsonWriterNodes.value(levelValue); // a bigger font jsonWriterNodes.key(JSON_FONT); jsonWriterNodes.object(); jsonWriterNodes.key(JSON_FONT_SIZE); jsonWriterNodes.value(JSON_FONT_SIZE_VALUE); jsonWriterNodes.endObject(); jsonWriterNodes.endObject(); // end node object nodeWriter.newLine(); if (writeNodeImmediately) { nodeWriter.flush(); } } private void writeJsonEdge(SNode fromNode, SNode toNode, SRelation relation) throws IOException, SaltParameterException { // get class of fromNode String edgeColor; if (fromNode instanceof SToken) { edgeColor = TOK_BORDER_COLOR_VALUE; } else if (fromNode instanceof SSpan) { edgeColor = SPAN_BORDER_COLOR_VALUE; } else if (fromNode instanceof SStructure) { edgeColor = STRUCTURE_BORDER_COLOR_VALUE; } else { throw new SaltParameterException(fromNode.getId(), "writeJsonEdge", this.getClass()); } jsonWriterEdges.object(); jsonWriterEdges.key(JSON_EDGE_FROM); jsonWriterEdges.value(fromNode.getPath().fragment()); jsonWriterEdges.key(JSON_EDGE_TO); jsonWriterEdges.value(toNode.getPath().fragment()); Set<SAnnotation> sAnnotations = relation.getAnnotations(); if (sAnnotations.size() > 0) { StringBuilder allLabels = new StringBuilder(); List<Map.Entry<String, String>> sortedAnnotations = sortAnnotations(sAnnotations); int i = 0; for (Map.Entry<String, String> annotation : sortedAnnotations) { allLabels.append(annotation.getKey()).append("=").append(annotation.getValue()); if (i < sAnnotations.size()) { allLabels.append(NEWLINE); } i++; } jsonWriterEdges.key(JSON_LABEL); jsonWriterEdges.value(allLabels.toString()); } jsonWriterEdges.key(JSON_WIDTH); jsonWriterEdges.value(EDGE_WIDTH); jsonWriterEdges.key(JSON_COLOR); jsonWriterEdges.value(edgeColor); if (relation instanceof SPointingRelation) { jsonWriterEdges.key(JSON_SMOOTH); jsonWriterEdges.object(); jsonWriterEdges.key(JSON_TYPE); jsonWriterEdges.value(JSON_EDGE_TYPE_VALUE); jsonWriterEdges.key(JSON_ROUNDNESS); jsonWriterEdges.value(JSON_ROUNDNESS_VALUE); jsonWriterEdges.endObject(); } jsonWriterEdges.endObject(); edgeWriter.newLine(); } /* * Determine the max. level for JSON node objects. */ private int getMaxLevel(SDocument doc) { maxLevel = getMaxHeightOfSDocGraph(doc); int nSpanClasses = spanClasses.size(); // set nGroupsId if (readSpanNodes != null && (readSpanNodes.size() > 0)) { nGroupsId += 1; } if (readStructNodes != null && (readStructNodes.size() > 0)) { nGroupsId += 2; } // maxLevel depends on node groups if (nGroupsId == 3) { maxLevel += nSpanClasses; } else if (nGroupsId == 1) { maxLevel += (nSpanClasses - 1); } if (readSpanNodes != null) { readSpanNodes.clear(); } if (readStructNodes != null) { readStructNodes.clear(); } // If maxLevel > 0, there are further nodes beside token nodes, since // token nodes are mandatory. // Thus, graph will be rendered with physics. if (maxLevel > 0) { withPhysics = true; } return maxLevel; } // traverse the graph in depth first top down mode beginning with its roots // and calculate the max. height of the salt graph private int getMaxHeightOfSDocGraph(SDocument doc) { doc.getDocumentGraph().traverse(doc.getDocumentGraph().getRoots(), GRAPH_TRAVERSE_TYPE.TOP_DOWN_DEPTH_FIRST, TRAV_MODE_CALC_LEVEL, this); if (maxHeight > (Integer.MAX_VALUE - 100)) { throw new SaltException("The specified document cannot be visualized. It is too complex."); } else { return (int) maxHeight; } } /** * Implements the nodeReached method of the * {@link org.corpus_tools.salt.core.GraphTraverseHandler} interface. */ @Override public void nodeReached(GRAPH_TRAVERSE_TYPE traversalType, String traversalId, SNode currNode, SRelation relation, SNode fromNode, long order) { if (traversalType == GRAPH_TRAVERSE_TYPE.TOP_DOWN_DEPTH_FIRST) { if (traversalId.equals(TRAV_MODE_CALC_LEVEL) && (exportFilter == null || exportFilter.includeNode(currNode))) { if (relation != null && !(relation instanceof SPointingRelation) && !(fromNode instanceof SToken)) { currHeight++; if (maxHeight < currHeight) { maxHeight = currHeight; } } if ((currNode instanceof SSpan)) { String annClass = ""; Set<SAnnotation> sAnnotations = currNode.getAnnotations(); if (sAnnotations.size() > 0) { List<Map.Entry<String, String>> sortedAnnotations = sortAnnotations(sAnnotations); // use the first annotation annClass = sortedAnnotations.iterator().next().getKey(); } if (!spanClasses.containsKey(annClass)) { spanClasses.put(annClass, -1); } readSpanNodes.add(currNode); } if ((currNode instanceof SStructure)) { readStructNodes.add(currNode); } } } } /** * Implements the nodeLeft method of the * {@link org.corpus_tools.salt.core.GraphTraverseHandler} interface. */ @Override public void nodeLeft(GRAPH_TRAVERSE_TYPE traversalType, String traversalId, SNode currNode, SRelation relation, SNode fromNode, long order) { if (traversalType == GRAPH_TRAVERSE_TYPE.TOP_DOWN_DEPTH_FIRST) { if (traversalId.equals(TRAV_MODE_CALC_LEVEL)) { if (!(relation instanceof SPointingRelation) && !(fromNode instanceof SToken) && (exportFilter == null || exportFilter.includeNode(currNode)) && (relation != null)) { currHeight--; } } else if (traversalId.equals(TRAV_MODE_READ_NODES)) { if (currNode instanceof SToken) { currHeightFromToken = 1; // write SSpan-Nodes if ((fromNode instanceof SSpan) && (!readSpanNodes.contains(fromNode)) && (exportFilter == null || exportFilter.includeNode(fromNode))) { String annotation = ""; Set<SAnnotation> sAnnotations = fromNode.getAnnotations(); if (sAnnotations.size() > 0) { List<Map.Entry<String, String>> sortedAnnotations = sortAnnotations(sAnnotations); // use first annotation annotation = sortedAnnotations.iterator().next().getKey(); } int spanOffset = spanClasses.get(annotation); if (spanOffset == -1) { maxSpanOffset = Math.max(spanOffset, maxSpanOffset) + 1; spanClasses.put(annotation, maxSpanOffset); } spanOffset = spanClasses.get(annotation); try { writeJsonNode(fromNode, maxLevel - currHeightFromToken - spanOffset); nNodes++; } catch (IOException e) { throw new SaltException("A problem occurred while building JSON objects."); } readSpanNodes.add(fromNode); } } else if (currNode instanceof SStructure) { if ((exportFilter == null || exportFilter.includeNode(currNode)) && !readStructNodes.contains(currNode)) { currHeightFromToken++; int currLevel = maxLevel - currHeightFromToken - spanClasses.size() + 1; try { // if currNode is a root if (roots.contains(currNode)) { int minRootLevel; if (rootToMinLevel.containsKey(currNode)) { minRootLevel = Math.min(rootToMinLevel.get(currNode), currLevel); } else { minRootLevel = currLevel; } // write root node with min level writeJsonNode(currNode, minRootLevel); } else { writeJsonNode(currNode, currLevel); } } catch (IOException e) { throw new SaltException("A problem occurred while building JSON objects."); } nNodes++; readStructNodes.add(currNode); } // if fromNode is a root, store its min level if (fromNode instanceof SStructure && roots.contains(fromNode) && !readStructNodes.contains(fromNode) && (exportFilter == null || exportFilter.includeNode(fromNode))) { int thisRootLevel = maxLevel - currHeightFromToken - spanClasses.size(); if (rootToMinLevel.containsKey(fromNode)) { int oldLevel = rootToMinLevel.get(fromNode); rootToMinLevel.put(fromNode, Math.min(oldLevel, thisRootLevel)); } else { rootToMinLevel.put(fromNode, thisRootLevel); } } } if (relation != null) { if (!readRelations.contains(relation) && (exportFilter == null || (exportFilter.includeRelation(relation) && exportFilter.includeNode(fromNode) && exportFilter.includeNode(currNode)))) { try { writeJsonEdge(fromNode, currNode, relation); } catch (IOException e) { throw new SaltException("A problem occurred while building JSON objects."); } readRelations.add(relation); } } } } } /** * Implements the checkConstraint method of the * {@link org.corpus_tools.salt.core.GraphTraverseHandler} interface. */ @Override public boolean checkConstraint(GRAPH_TRAVERSE_TYPE traversalType, String traversalId, SRelation relation, SNode currNode, long order) { if (relation instanceof SDominanceRelation || relation instanceof SSpanningRelation || relation instanceof SPointingRelation || relation == null) { return true; } else { return false; } } }