annis.visualizers.htmlvis.HTMLVis Maven / Gradle / Ivy
/*
* Copyright 2013 SFB 632.
*
* 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 annis.visualizers.htmlvis;
import annis.CommonHelper;
import annis.libgui.AnnisBaseUI;
import annis.libgui.Helper;
import annis.libgui.MatchedNodeColors;
import annis.libgui.VisualizationToggle;
import annis.libgui.visualizers.AbstractVisualizer;
import annis.libgui.visualizers.VisualizerInput;
import annis.model.Annotation;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.gwt.thirdparty.guava.common.base.Objects;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import org.apache.commons.io.IOUtils;
import org.corpus_tools.salt.common.SDocumentGraph;
import org.corpus_tools.salt.common.SSpan;
import org.corpus_tools.salt.common.SToken;
import org.corpus_tools.salt.core.SNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*
* Mappings:
*
* - config - path of the visualization configuration file
* - hitmark - if "true" (which is the default) hit are marked in their
* corresponding colors
*
*
*
* @author Thomas Krause
*/
@PluginImplementation
public class HTMLVis extends AbstractVisualizer
{
private static final Logger log = LoggerFactory.getLogger(HTMLVis.class);
private final static Escaper urlPathEscape = UrlEscapers.
urlPathSegmentEscaper();
private Map mc;
private String tokenColor = "";
private boolean hitMark = true;
@Override
public String getShortName()
{
return "html";
}
@Override
public boolean isUsingText()
{
return false;
}
@Override
public Panel createComponent(VisualizerInput vi, VisualizationToggle vt)
{
Panel scrollPanel = new Panel();
scrollPanel.setSizeFull();
Label lblResult = new Label("ERROR", ContentMode.HTML);
lblResult.setSizeUndefined();
List corpusPath = CommonHelper.getCorpusPath(vi.getDocument().
getGraph(), vi.getDocument());
String corpusName = corpusPath.get(corpusPath.size() - 1);
corpusName = urlPathEscape.escape(corpusName);
String wrapperClassName = "annis-wrapped-htmlvis-"
+ corpusName.replaceAll("[^0-9A-Za-z-]", "_");
scrollPanel.addStyleName(wrapperClassName);
String visConfigName = vi.getMappings().getProperty("config");
String hitMarkConfig = vi.getMappings().getProperty("hitmark", "true");
hitMark = Boolean.parseBoolean(hitMarkConfig);
mc = vi.getMarkedAndCovered();
VisualizationDefinition[] definitions = parseDefinitions(corpusName, vi.
getMappings());
if (definitions != null)
{
lblResult.setValue(createHTML(vi.getSResult().getDocumentGraph(),
definitions));
String labelClass = vi.getMappings().getProperty("class", "htmlvis");
lblResult.addStyleName(labelClass);
InputStream inStreamCSSRaw = null;
if (visConfigName == null)
{
inStreamCSSRaw = HTMLVis.class.getResourceAsStream("htmlvis.css");
}
else
{
WebResource resBinary = Helper.getAnnisWebResource().path(
"query/corpora/").path(corpusName).path(corpusName)
.path("binary").path(visConfigName + ".css");
ClientResponse response = resBinary.get(ClientResponse.class);
if (response.getStatus() == ClientResponse.Status.OK.getStatusCode())
{
inStreamCSSRaw = response.getEntityInputStream();
}
}
if (inStreamCSSRaw != null)
{
try (InputStream inStreamCSS = inStreamCSSRaw)
{
String cssContent = IOUtils.toString(inStreamCSS);
UI currentUI = UI.getCurrent();
if (currentUI instanceof AnnisBaseUI)
{
// do not add identical CSS files
((AnnisBaseUI) currentUI).injectUniqueCSS(cssContent,
wrapperClassName);
}
}
catch (IOException ex)
{
log.error("Could not parse the HTML visualizer CSS file",
ex);
Notification.show(
"Could not parse the HTML visualizer CSS file", ex.
getMessage(),
Notification.Type.ERROR_MESSAGE);
}
}
}
if (vi.getMappings().containsKey("debug"))
{
TextArea txtDebug = new TextArea();
txtDebug.setValue(lblResult.getValue());
txtDebug.setReadOnly(true);
txtDebug.setWidth("100%");
Label sep = new Label("
", ContentMode.HTML);
VerticalLayout layout = new VerticalLayout(txtDebug, sep, lblResult);
layout.setSizeUndefined();
scrollPanel.setContent(layout);
}
else
{
scrollPanel.setContent(lblResult);
}
return scrollPanel;
}
@Override
public List getFilteredNodeAnnotationNames(String toplevelCorpusName,
String documentName, Properties mappings)
{
Set result = null;
VisualizationDefinition[] definitions = parseDefinitions(toplevelCorpusName,
mappings);
if (definitions != null)
{
for (VisualizationDefinition def : definitions)
{
List sub = def.getMatcher().getRequiredAnnotationNames();
if (sub == null)
{
// a rule requires all annotations, abort
result = null;
break;
}
else
{
if (result == null)
{
result = new LinkedHashSet<>();
}
result.addAll(sub);
}
}
}
if (result == null)
{
return null;
}
else
{
return new LinkedList<>(result);
}
}
public VisualizationDefinition[] parseDefinitions(String toplevelCorpusName,
Properties mappings)
{
InputStream inStreamConfigRaw = null;
String visConfigName = mappings.getProperty("config");
if (visConfigName == null)
{
inStreamConfigRaw = HTMLVis.class.getResourceAsStream("defaultvis.config");
}
else
{
WebResource resBinary = Helper.getAnnisWebResource().path(
"query/corpora/").path(toplevelCorpusName).path(toplevelCorpusName)
.path("binary").path(visConfigName + ".config");
ClientResponse response = resBinary.get(ClientResponse.class);
if (response.getStatus() == ClientResponse.Status.OK.getStatusCode())
{
inStreamConfigRaw = response.getEntityInputStream();
}
}
if (inStreamConfigRaw == null)
{
Notification.show("ERROR: html visualization configuration \""
+ visConfigName
+ "\" not found in database", Notification.Type.ERROR_MESSAGE);
}
else
{
try (InputStream inStreamConfig = inStreamConfigRaw)
{
VisParser p = new VisParser(inStreamConfig);
return p.getDefinitions();
}
catch (IOException | VisParserException ex)
{
log.error("Could not parse the HTML visualization configuration file",
ex);
Notification.show(
"Could not parse the HTML visualization configuration file", ex.
getMessage(),
Notification.Type.ERROR_MESSAGE);
}
}
return null;
}
public String createHTML(SDocumentGraph graph,
VisualizationDefinition[] definitions)
{
HashMap instruction_priorities = new HashMap<>();
SortedMap> outputStartTags
= new TreeMap<>();
SortedMap> outputEndTags
= new TreeMap<>();
StringBuilder sb = new StringBuilder();
List token = graph.getSortedTokenByText();
//Get metadata for visualizer if stylesheet requires it
//First check the stylesheet
Boolean bolMetaTypeFound = false;
HashMap meta = new HashMap<>();
int def_priority = 0;
for (VisualizationDefinition vis : definitions)
{
if (vis.getOutputter().getType() == SpanHTMLOutputter.Type.META_NAME)
{
bolMetaTypeFound = true;
}
else //not a meta-annotation, remember order in config file to set priority
{
if (vis.getMatcher() instanceof AnnotationNameMatcher)
{
instruction_priorities.put(vis, def_priority);
}
else if (vis.getMatcher() instanceof AnnotationNameAndValueMatcher)
{
instruction_priorities.put(vis, def_priority);
}
else if (vis.getMatcher() instanceof TokenMatcher)
{
instruction_priorities.put(vis, def_priority);
}
def_priority--;
}
vis.getOutputter().setMeta(meta);
}
if (bolMetaTypeFound == true) //Metadata is required, get corpus and document name
{
//Get corpus and document name
String strDocName = "";
String strCorpName = "";
strDocName = graph.getDocument().getName();
List corpusPath = CommonHelper.getCorpusPath(graph.getDocument().
getGraph(), graph.getDocument());
strCorpName = corpusPath.get(corpusPath.size() - 1);
//Get metadata and put in hashmap
List metaData = Helper.getMetaDataDoc(strCorpName, strDocName);
for (Annotation metaDatum : metaData)
{
meta.put(metaDatum.getName(), metaDatum.getValue());
}
}
for (SToken t : token)
{
tokenColor = "";
if (mc.containsKey(t) && hitMark)
{
tokenColor = MatchedNodeColors
.getHTMLColorByMatch(mc.get(t));
}
for (VisualizationDefinition vis : definitions)
{
String matched = vis.getMatcher().matchedAnnotation(t);
if (matched != null)
{
vis.getOutputter().outputHTML(t, matched, outputStartTags,
outputEndTags, tokenColor, Objects.firstNonNull(instruction_priorities.get(vis), 0));
}
}
}
List spans = graph.getSpans();
for (VisualizationDefinition vis : definitions)
{
for (SSpan span : spans)
{
tokenColor = "";
if (mc.containsKey(span) && hitMark)
{
tokenColor = MatchedNodeColors
.getHTMLColorByMatch(mc.get(span));
}
String matched = vis.getMatcher().matchedAnnotation(span);
if (matched != null)
{
vis.getOutputter().outputHTML(span, matched, outputStartTags,
outputEndTags, tokenColor, Objects.firstNonNull(instruction_priorities.get(vis), 0));
}
}
}
int minStartTagPos = outputStartTags.firstKey().intValue();
int maxEndTagPos = outputEndTags.lastKey().intValue();
//Find BEGIN and END instructions if available
for (VisualizationDefinition vis : definitions)
{
if (vis.getMatcher() instanceof PseudoRegionMatcher)
{
PseudoRegionMatcher.PseudoRegion psdRegionType = ((PseudoRegionMatcher) vis.
getMatcher()).getPsdRegion();
int positionStart = 0;
int positionEnd = 0;
if (!outputEndTags.isEmpty() && !outputStartTags.isEmpty() && psdRegionType != null)
{
switch (psdRegionType)
{
case BEGIN:
positionStart = positionEnd = Integer.MIN_VALUE;
// def_priority is now lower than all normal annotation
instruction_priorities.put(vis, def_priority);
break;
case END:
positionStart = positionEnd = Integer.MAX_VALUE;
// def_priority is now lower than all normal annotation
instruction_priorities.put(vis, def_priority);
break;
case ALL:
// use same position as last and first key
positionStart = minStartTagPos;
positionEnd = maxEndTagPos;
// The ALL pseudo-range must enclose everything, thus it get the
// priority which is one lower than the smallest non BEGIN/END
// priority.
instruction_priorities.put(vis, def_priority);
break;
default:
break;
}
}
switch (vis.getOutputter().getType())
{
case META_NAME:
String strMetaVal = meta.
get(vis.getOutputter().getMetaname().trim());
if (strMetaVal == null)
{
throw new NullPointerException(
"no such metadata name in document: '" + vis.getOutputter().
getMetaname().trim() + "'");
}
else
{
vis.getOutputter().outputAny(positionStart, positionEnd,
((PseudoRegionMatcher) vis.getMatcher()).getAnnotationName(),
strMetaVal, outputStartTags, outputEndTags,
Objects.firstNonNull(instruction_priorities.get(vis), 0));
}
break;
case CONSTANT:
vis.getOutputter().outputAny(positionStart, positionEnd,
((PseudoRegionMatcher) vis.getMatcher()).getAnnotationName(),
vis.getOutputter().getConstant(), outputStartTags, outputEndTags,
Objects.firstNonNull(instruction_priorities.get(vis), 0));
break;
case EMPTY:
vis.getOutputter().outputAny(positionStart, positionEnd,
((PseudoRegionMatcher) vis.getMatcher()).getAnnotationName(),
"", outputStartTags, outputEndTags,
Objects.firstNonNull(instruction_priorities.get(vis), 0));
break;
case ANNO_NAME:
break; //this shouldn't happen, since the BEGIN/END instruction has no triggering annotation name or value
case VALUE:
break; //this shouldn't happen, since the BEGIN/END instruction has no triggering annotation name or value
case ESCAPED_VALUE:
break; //this shouldn't happen, since the BEGIN/END instruction has no triggering annotation name or value
default:
}
}
}
// get all used indexes
Set indexes = new TreeSet<>();
indexes.addAll(outputStartTags.keySet());
indexes.addAll(outputEndTags.keySet());
for (Long i : indexes)
{
// output all strings belonging to this token position
// first the start tags for this position
// add priorities from instruction_priorities for sorting length ties
List unsortedStart = outputStartTags.get(i);
SortedSet itemsStart = new TreeSet();
if (unsortedStart != null)
{
Iterator it = unsortedStart.iterator();
while (it.hasNext())
{
OutputItem s = it.next();
itemsStart.add(s);
}
}
{
Iterator it = itemsStart.iterator();
boolean first = true;
while (it.hasNext())
{
OutputItem s = it.next();
if (!first)
{
sb.append("-->");
}
first = false;
sb.append(s.getOutputString());
if (it.hasNext())
{
sb.append("