annis.visualizers.htmlvis.HTMLVis.java Source code

Java tutorial

Introduction

Here is the source code for annis.visualizers.htmlvis.HTMLVis.java

Source

/*
 * 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;

/**
 *
 * <p>
 * <strong>Mappings:</strong><br />
 * <ul>
 * <li>config - path of the visualization configuration file</li>
 * <li>hitmark - if "true" (which is the default) hit are marked in their
 * corresponding colors</li>
 * </ul>
 * </p>
 *
 * @author Thomas Krause <krauseto@hu-berlin.de>
 */
@PluginImplementation
public class HTMLVis extends AbstractVisualizer<Panel> {

    private static final Logger log = LoggerFactory.getLogger(HTMLVis.class);

    private final static Escaper urlPathEscape = UrlEscapers.urlPathSegmentEscaper();

    private Map<SNode, Long> 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<String> 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("<hr/>", ContentMode.HTML);
            VerticalLayout layout = new VerticalLayout(txtDebug, sep, lblResult);
            layout.setSizeUndefined();
            scrollPanel.setContent(layout);
        } else {
            scrollPanel.setContent(lblResult);
        }

        return scrollPanel;
    }

    @Override
    public List<String> getFilteredNodeAnnotationNames(String toplevelCorpusName, String documentName,
            Properties mappings) {
        Set<String> result = null;

        VisualizationDefinition[] definitions = parseDefinitions(toplevelCorpusName, mappings);

        if (definitions != null) {
            for (VisualizationDefinition def : definitions) {
                List<String> 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<VisualizationDefinition, Integer> instruction_priorities = new HashMap<>();

        SortedMap<Long, List<OutputItem>> outputStartTags = new TreeMap<>();
        SortedMap<Long, List<OutputItem>> outputEndTags = new TreeMap<>();
        StringBuilder sb = new StringBuilder();

        List<SToken> token = graph.getSortedTokenByText();

        //Get metadata for visualizer if stylesheet requires it
        //First check the stylesheet
        Boolean bolMetaTypeFound = false;

        HashMap<String, String> 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<String> corpusPath = CommonHelper.getCorpusPath(graph.getDocument().getGraph(),
                    graph.getDocument());
            strCorpName = corpusPath.get(corpusPath.size() - 1);

            //Get metadata and put in hashmap
            List<Annotation> 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<SSpan> 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<Long> 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<OutputItem> unsortedStart = outputStartTags.get(i);
            SortedSet<OutputItem> itemsStart = new TreeSet();
            if (unsortedStart != null) {
                Iterator<OutputItem> it = unsortedStart.iterator();
                while (it.hasNext()) {
                    OutputItem s = it.next();
                    itemsStart.add(s);
                }
            }

            {
                Iterator<OutputItem> 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("<!--\n");
                    }
                }
            }
            // then the end tags for this position, but inverse their order
            List<OutputItem> unsortedEnd = outputEndTags.get(i);
            SortedSet<OutputItem> itemsEnd = new TreeSet();
            if (unsortedEnd != null) {
                Iterator<OutputItem> it = unsortedEnd.iterator();
                while (it.hasNext()) {
                    OutputItem s = it.next();
                    itemsEnd.add(s);
                }
            }

            {
                List<OutputItem> itemsEndReverse = new LinkedList<>(itemsEnd);
                Collections.reverse(itemsEndReverse);
                for (OutputItem s : itemsEndReverse) {
                    sb.append(s.getOutputString());
                }
            }

        }

        return sb.toString();
    }

}