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

annis.visualizers.component.grid.GridComponent Maven / Gradle / Ivy

There is a newer version: 4.0.0-beta.4
Show newest version
/*
 * Copyright 2014 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.component.grid;

import annis.CommonHelper;
import annis.gui.widgets.grid.AnnotationGrid;
import annis.gui.widgets.grid.GridEvent;
import annis.gui.widgets.grid.Row;
import annis.libgui.Helper;
import annis.libgui.media.MediaController;
import annis.libgui.media.PDFController;
import annis.libgui.visualizers.VisualizerInput;
import annis.model.AnnisConstants;
import static annis.model.AnnisConstants.ANNIS_NS;
import static annis.model.AnnisConstants.FEAT_MATCHEDNODE;
import annis.model.RelannisNodeFeature;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ChameleonTheme;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import org.corpus_tools.salt.common.SDocumentGraph;
import org.corpus_tools.salt.common.SSpan;
import org.corpus_tools.salt.common.STextualDS;
import org.corpus_tools.salt.common.SToken;
import org.corpus_tools.salt.core.SAnnotation;
import org.corpus_tools.salt.core.SFeature;
import org.corpus_tools.salt.core.SNode;
import org.eclipse.emf.common.util.EList;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Thomas Krause 
 */
public class GridComponent extends Panel
{

  private static final org.slf4j.Logger log
    = LoggerFactory.getLogger(GridComponent.class);
  public static final String MAPPING_ANNOS_KEY = "annos";
  public static final String MAPPING_ANNO_REGEX_KEY = "anno_regex";
  public static final String MAPPING_HIDE_TOK_KEY = "hide_tok";
  public static final String MAPPING_TOK_ANNOS_KEY = "tok_anno";
  public static final String MAPPING_ESCAPE_HTML = "escape_html";
  public static final String MAPPING_SHOW_NAMESPACE = "show_ns";
  
  private AnnotationGrid grid;
  private final transient VisualizerInput input;
  private final transient MediaController mediaController;
  private final transient PDFController pdfController;
  private final VerticalLayout layout;
  private Set manuallySelectedTokenAnnos;
  private String segmentationName;
  private final transient STextualDS enforcedText;
  private final Label lblEmptyToken;
  
  public enum ElementType
  {

    begin, end, middle, single, noEvent
  }

  public GridComponent(VisualizerInput input, MediaController mediaController,
    PDFController pdfController, boolean forceToken, STextualDS enforcedText)
  {
    this.input = input;
    this.mediaController = mediaController;
    this.pdfController = pdfController;
    this.enforcedText = enforcedText;
    
    setWidth("100%");
    setHeight("-1");
    layout = new VerticalLayout();
    setContent(layout);
    layout.setSizeUndefined();
    addStyleName(ChameleonTheme.PANEL_BORDERLESS);
    
    lblEmptyToken = new Label("(Empty token list, you may want to select another base text from the menu above.)");
    lblEmptyToken.setVisible(false);
    lblEmptyToken.addStyleName("empty_token_hint");
    layout.addComponent(lblEmptyToken);
    if (input != null)
    {
      this.manuallySelectedTokenAnnos = input.getVisibleTokenAnnos();
      this.segmentationName = forceToken ? null :  input.getSegmentationName();
      
      List texts
        = input.getDocument().getDocumentGraph().getTextualDSs();
      if (texts != null && texts.size() > 0 && !Helper.isRTLDisabled())
      {
        if (CommonHelper.containsRTLText(texts.get(0).getText()))
        {
          addStyleName("rtl");
        }
      }

      createAnnotationGrid();
    } // end if input not null
    

  }


  private void createAnnotationGrid()
  {
    String resultID = input.getId();
    grid = new AnnotationGrid(mediaController, pdfController, resultID);
    grid.addStyleName(getMainStyle());
    grid.addStyleName(Helper.CORPUS_FONT_FORCE);
    grid.setEscapeHTML(Boolean.parseBoolean(input.getMappings().
      getProperty(MAPPING_ESCAPE_HTML, "true")));
    LinkedList> types = new LinkedList<>();
    if(isShowingSpanAnnotations())
    {
      types.add(SSpan.class);
    }
    if(isShowingTokenAnnotations())
    {
      types.add(SToken.class);
    }
    grid.setAnnosWithNamespace(EventExtractor.computeDisplayedNamespace(input, types));
    
    
    layout.addComponent(grid);
    SDocumentGraph graph = input.getDocument().getDocumentGraph();
    
    List tokens = CommonHelper.getSortedSegmentationNodes(segmentationName,
      graph);
    Preconditions.checkArgument(!tokens.isEmpty(), "Token list must be non-empty");
    RelannisNodeFeature featTokStart
      = (RelannisNodeFeature) tokens.get(0).
      getFeature(AnnisConstants.ANNIS_NS, AnnisConstants.FEAT_RELANNIS_NODE).
      getValue();
    long startIndex = featTokStart.getTokenIndex();
    RelannisNodeFeature featTokEnd
      = (RelannisNodeFeature) tokens.get(tokens.size() - 1).
      getFeature(AnnisConstants.ANNIS_NS, AnnisConstants.FEAT_RELANNIS_NODE).
      getValue();
    long endIndex = featTokEnd.getTokenIndex();
    
    LinkedHashMap> rowsByAnnotation = 
      computeAnnotationRows(startIndex, endIndex);
    
    // add tokens as row
    AtomicInteger tokenOffsetForText = new AtomicInteger(-1);
    Row tokenRow = computeTokenRow(tokens, graph,
      rowsByAnnotation, startIndex, tokenOffsetForText);
    if (isHidingToken() == false)
    {
      
      if(isTokenFirst())
      {
        // copy original list but add token row at the beginning
        LinkedHashMap> newList = new LinkedHashMap<>();
        
        newList.put("tok", Lists.newArrayList(tokenRow));
        newList.putAll(rowsByAnnotation);
        rowsByAnnotation = newList;
        
      }
      else
      {
        // just add the token row to the end of the list
        rowsByAnnotation.put("tok", Lists.newArrayList(tokenRow));
      }
    }
    
    EventExtractor.removeEmptySpace(rowsByAnnotation, tokenRow);
    
    // check if the token row only contains empty values
    boolean tokenRowIsEmpty = true;
    for(GridEvent tokenEvent : tokenRow.getEvents())
    {
      if(tokenEvent.getValue() != null && !tokenEvent.getValue().trim().isEmpty())
      {
        tokenRowIsEmpty = false;
        break;
      }
    }
    if(!isHidingToken() && canShowEmptyTokenWarning())
    {
      lblEmptyToken.setVisible(tokenRowIsEmpty);
    }
    grid.setRowsByAnnotation(rowsByAnnotation);
    grid.setTokenIndexOffset(tokenOffsetForText.get());
  }
  
  private Row computeTokenRow(List tokens, 
    SDocumentGraph graph, LinkedHashMap> rowsByAnnotation,
    long startIndex, AtomicInteger tokenOffsetForText)
  {
    /* we will only add tokens of one texts which is mentioned by any
      included annotation. */
    Set validTextIDs = new HashSet<>();
    
    if(enforcedText == null)
    {
      Iterator> itAllRows = rowsByAnnotation.values().iterator();
      while (itAllRows.hasNext())
      {
        ArrayList rowsForAnnotation = itAllRows.next();
        for (Row r : rowsForAnnotation)
        {
          validTextIDs.addAll(r.getTextIDs());
        }
      }
      /**
       * we want to show all token if no valid text was found and we have only one
       * text and the first one if there are more than one text.
       */
      List allTexts = graph.getTextualDSs();
      if (validTextIDs.isEmpty() && allTexts != null && (allTexts.size() == 1
        || allTexts.size() == 2))
      {
        validTextIDs.add(allTexts.get(0).getId());
      }
    }
    else
    {
      validTextIDs.add(enforcedText.getId());
    }
    
    Row tokenRow = new Row();
    for (SNode t : tokens)
    {
      // get the Salt ID of the STextualDS of this token
      STextualDS tokenText = CommonHelper.getTextualDSForNode(t, graph);

      // only add token if text ID matches the valid one
      if (tokenText != null && validTextIDs.contains(tokenText.getId()))
      {
        RelannisNodeFeature feat
          = (RelannisNodeFeature) t.getFeature(AnnisConstants.ANNIS_NS,
            AnnisConstants.FEAT_RELANNIS_NODE).getValue();
        long idxLeft = feat.getLeftToken() - startIndex;
        long idxRight = feat.getRightToken() - startIndex;
        if (tokenOffsetForText.get() < 0)
        {
          // set the token offset by assuming the first idx must be zero
          tokenOffsetForText.set(Math.abs((int) idxLeft));
        }
        String text = extractTextForToken(t, segmentationName);
        GridEvent event
          = new GridEvent(t.getId(), (int) idxLeft, (int) idxRight, text);
        event.setTextID(tokenText.getId());
        // check if the token is a matched node
        Long match = isCoveredTokenMarked() ? 
          markCoveredTokens(input.getMarkedAndCovered(), t) : tokenMatch(t);
        event.setMatch(match);
        tokenRow.addEvent(event);
      }
    } // end token row
    
    return tokenRow;
  }
  
  private String extractTextForToken(SNode t, String segmentation)
  {
    if(t instanceof SToken)
    {
      return CommonHelper.getSpannedText((SToken) t);
    }
    else if(segmentation != null)
    {
      for(SAnnotation anno : t.getAnnotations())
      {
        if(anno.getName().equals(segmentation))
        {
          return anno.getValue_STEXT();
        }
      }
    }
    return "";
  }
  
  private LinkedHashMap> computeAnnotationRows(
    long startIndex, long endIndex)
  {
    List annos = new LinkedList<>();
    
    boolean showSpanAnnotations = isShowingSpanAnnotations();
    if(showSpanAnnotations)
    {
      annos.addAll(EventExtractor.computeDisplayAnnotations(input, SSpan.class));
    }
    
    boolean showTokenAnnotations = isShowingTokenAnnotations();
    
    if (showTokenAnnotations)
    {
      List tokenAnnos
        = EventExtractor.computeDisplayAnnotations(input, SToken.class);
      if(manuallySelectedTokenAnnos != null)
      {
        tokenAnnos.retainAll(manuallySelectedTokenAnnos);
      }
      annos.addAll(tokenAnnos);
    }
    
     // search for media annotations
    Set mediaAnnotations = null;
    
    if(isFilteringMediaLayer())
    {
      mediaAnnotations = new HashSet<>();
      Pattern patternMedia = Pattern.compile("(annis::)?time");
      for (String qname : annos)
      {
        if(patternMedia.matcher(qname).matches())
        {
          mediaAnnotations.add(qname);
        }
      }
    }
    
    LinkedHashMap> rowsByAnnotation
      = EventExtractor.parseSalt(input, showSpanAnnotations, 
        showTokenAnnotations, annos, mediaAnnotations, isAddingPlaybackRow(),
        (int) startIndex, (int) endIndex, pdfController, enforcedText);
    
    return rowsByAnnotation;
  }
  
  public void setVisibleTokenAnnos(Set annos)
  {
    this.manuallySelectedTokenAnnos = annos;
    // complete recreation of the grid
    layout.removeComponent(grid);
    createAnnotationGrid();
    
  }
  
  public void setSegmentationLayer(String segmentationName, 
    Map markedAndCovered)
  {
    this.segmentationName = segmentationName;
    this.input.setMarkedAndCovered(markedAndCovered);
    // complete recreation of the grid
    layout.removeComponent(grid);
    createAnnotationGrid();
  }
  
  protected boolean isShowingTokenAnnotations()
  {
    return Boolean.parseBoolean(input.getMappings().
        getProperty(MAPPING_TOK_ANNOS_KEY));
  }
  
  protected boolean isShowingSpanAnnotations()
  {
    return true;
  }
  
  protected boolean isHidingToken()
  {
    return Boolean.parseBoolean(input.getMappings().
      getProperty(MAPPING_HIDE_TOK_KEY, "false"));
    
  }
  
  protected boolean isTokenFirst()
  {
    return false;
  }
  
  protected boolean isFilteringMediaLayer()
  {
    return false;
  }
  
  protected boolean isAddingPlaybackRow()
  {
    return false;
  }
  
  protected boolean canShowEmptyTokenWarning()
  {
    return false;
  }
  
  protected boolean isCoveredTokenMarked()
  {
    return false;
  }
  
  protected String getMainStyle()
  {
    return "partitur_table";
  }
  
  /**
   * Checks if a token is covered by a matched node but not a match by it self.
   *
   * @param markedAndCovered A mapping from node to a matched number. The node
   * must not matched directly, but covered by a matched node.
   * @param tok the checked token.
   * @return Returns null, if token is not covered neither marked.
   */
  private Long markCoveredTokens(Map markedAndCovered, SNode tok)
  {
    RelannisNodeFeature f = RelannisNodeFeature.extract(tok);
    if (markedAndCovered.containsKey(tok) && f != null && f.getMatchedNode()
      == null)
    {
      return markedAndCovered.get(tok);
    }
    return f != null ? f.getMatchedNode() : null;
  }
  
  /**
   * Checks if a token is a marked match
   *
   * @param tok the checked token.
   * @return Returns null, if token is not marked.
   */
  private Long tokenMatch(SNode tok)
  {
    // check if the span is a matched node
    SFeature featMatched = tok.getFeature(ANNIS_NS, FEAT_MATCHEDNODE);
    Long matchRaw = featMatched == null ? null : featMatched.
      getValue_SNUMERIC();
    return matchRaw;
  }

  public VisualizerInput getInput()
  {
    return input;
  }

  public AnnotationGrid getGrid()
  {
    return grid;
  }
  
  

} // end GridVisualizerComponent




© 2015 - 2024 Weber Informatics LLC | Privacy Policy