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

opennlp.tools.parser.Parse Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 opennlp.tools.parser;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import opennlp.tools.util.Span;

/**
 * Data structure for holding parse constituents.
 */
public class Parse implements Cloneable, Comparable {

  public static final String BRACKET_LRB = "(";
  public static final String BRACKET_RRB = ")";
  public static final String BRACKET_LCB = "{";
  public static final String BRACKET_RCB = "}";
  public static final String BRACKET_LSB = "[";
  public static final String BRACKET_RSB = "]";

  /**
   * The text string on which this parse is based.
   * This object is shared among all parses for the same sentence.
   */
  private String text;

  /**
   * The character offsets into the text for this constituent.
   */
  private Span span;

  /**
   * The syntactic type of this parse.
   */
  private String type;

  /**
   * The sub-constituents of this parse.
   */
  private List parts;

  /**
   * The head parse of this parse. A parse can be its own head.
   */
  private Parse head;

  /**
   * A string used during parse construction to specify which
   * stage of parsing has been performed on this node.
   */
  private String label;

  /**
   * Index in the sentence of the head of this constituent.
   */
  private int headIndex;

  /**
   * The parent parse of this parse.
   */
  private Parse parent;

  /**
   * The probability associated with the syntactic type
   * assigned to this parse.
   */
  private double prob;

  /**
   * The string buffer used to track the derivation of this parse.
   */
  private StringBuffer derivation;

  /**
   * Specifies whether this constituent was built during the chunking phase.
   */
  private boolean isChunk;

  /**
   * The pattern used to find the base constituent label of a
   * Penn Treebank labeled constituent.
   */
  private static Pattern typePattern = Pattern.compile("^([^ =-]+)");

  /**
   * The pattern used to find the function tags.
   */
  private static Pattern funTypePattern = Pattern.compile("^[^ =-]+-([^ =-]+)");

  /**
   * The patter used to identify tokens in Penn Treebank labeled constituents.
   */
  private static Pattern tokenPattern = Pattern.compile("^[^ ()]+ ([^ ()]+)\\s*\\)");

  /**
   * The set of punctuation parses which are between this parse and the previous parse.
   */
  private Collection prevPunctSet;

  /**
   * The set of punctuation parses which are between this parse and
   * the subsequent parse.
   */
  private Collection nextPunctSet;

  /**
   * Specifies whether constituent labels should include parts specified
   * after minus character.
   */
  private static boolean useFunctionTags;

  /**
   * Creates a new parse node for this specified text and span of the specified type with the specified probability
   * and the specified head index.
   *
   * @param text The text of the sentence for which this node is a part of.
   * @param span The character offsets for this node within the specified text.
   * @param type The constituent label of this node.
   * @param p The probability of this parse.
   * @param index The token index of the head of this parse.
   */
  public Parse(String text, Span span, String type, double p, int index) {
    this.text = text;
    this.span = span;
    this.type = type;
    this.prob = p;
    this.head = this;
    this.headIndex = index;
    this.parts = new LinkedList<>();
    this.label = null;
    this.parent = null;
  }

  /**
   * Creates a new parse node for this specified text and span of the specified type with the specified probability
   * and the specified head and head index.
   *
   * @param text The text of the sentence for which this node is a part of.
   * @param span The character offsets for this node within the specified text.
   * @param type The constituent label of this node.
   * @param p The probability of this parse.
   * @param h The head token of this parse.
   */
  public Parse(String text, Span span, String type, double p, Parse h) {
    this(text, span, type, p, 0);
    if (h != null) {
      this.head = h;
      this.headIndex = h.headIndex;
    }
  }

  @Override
  public Object clone() {
    Parse p = new Parse(this.text, this.span, this.type, this.prob, this.head);
    p.parts = new LinkedList<>();
    p.parts.addAll(this.parts);

    if (derivation != null) {
      p.derivation = new StringBuffer(100);
      p.derivation.append(this.derivation.toString());
    }
    p.label = this.label;
    return (p);
  }

  /**
   * Clones the right frontier of parse up to the specified node.
   *
   * @param node The last node in the right frontier of the parse tree which should be cloned.
   * @return A clone of this parse and its right frontier up to and including the specified node.
   */
  public Parse clone(Parse node) {
    if (this == node) {
      return (Parse) this.clone();
    }
    else {
      Parse c = (Parse) this.clone();
      Parse lc = c.parts.get(parts.size()-1);
      c.parts.set(parts.size()-1,lc.clone(node));
      return c;
    }
  }
  /**
   * Clones the right frontier of this root parse up to and including the specified node.
   *
   * @param node The last node in the right frontier of the parse tree which should be cloned.
   * @param parseIndex The child index of the parse for this root node.
   * @return A clone of this root parse and its right frontier up to and including the specified node.
   */
  public Parse cloneRoot(Parse node, int parseIndex) {
    Parse c = (Parse) this.clone();
    Parse fc = c.parts.get(parseIndex);
    c.parts.set(parseIndex,fc.clone(node));
    return c;
  }

  /**
   * Specifies whether function tags should be included as part of the constituent type.
   *
   * @param uft true is they should be included; false otherwise.
   */
  public static void useFunctionTags(boolean uft) {
    useFunctionTags = uft;
  }


  /**
   * Set the type of this constituent to the specified type.
   *
   * @param type The type of this constituent.
   */
  public void setType(String type) {
    this.type = type;
  }

  /**
   * Returns the constituent label for this node of the parse.
   *
   * @return The constituent label for this node of the parse.
   */
  public String getType() {
    return type;
  }

  /**
   * Returns the set of punctuation parses that occur immediately before this parse.
   *
   * @return the set of punctuation parses that occur immediately before this parse.
   */
  public Collection getPreviousPunctuationSet() {
    return prevPunctSet;
  }

  /**
   * Designates that the specified punctuation should is prior to this parse.
   *
   * @param punct The punctuation.
   */
  public void addPreviousPunctuation(Parse punct) {
    if (this.prevPunctSet == null) {
      this.prevPunctSet = new TreeSet();
    }
    prevPunctSet.add(punct);
  }

  /**
   * Returns the set of punctuation parses that occur immediately after this parse.
   *
   * @return the set of punctuation parses that occur immediately after this parse.
   */
  public Collection getNextPunctuationSet() {
    return nextPunctSet;
  }

  /**
   * Designates that the specified punctuation follows this parse.
   *
   * @param punct The punctuation set.
   */
  public void addNextPunctuation(Parse punct) {
    if (this.nextPunctSet == null) {
      this.nextPunctSet = new TreeSet();
    }
    nextPunctSet.add(punct);
  }

  /**
   * Sets the set of punctuation tags which follow this parse.
   *
   * @param punctSet The set of punctuation tags which follow this parse.
   */
  public void setNextPunctuation(Collection punctSet) {
    this.nextPunctSet = punctSet;
  }

  /**
   * Sets the set of punctuation tags which preceed this parse.
   *
   * @param punctSet The set of punctuation tags which preceed this parse.
   */
  public void setPrevPunctuation(Collection punctSet) {
    this.prevPunctSet = punctSet;
  }

  /**
   * Inserts the specified constituent into this parse based on its text span.This
   * method assumes that the specified constituent can be inserted into this parse.
   *
   * @param constituent The constituent to be inserted.
   */
  public void insert(final Parse constituent) {
    Span ic = constituent.span;
    if (span.contains(ic)) {
      //double oprob=c.prob;
      int pi=0;
      int pn = parts.size();
      for (; pi < pn; pi++) {
        Parse subPart = parts.get(pi);
        //System.err.println("Parse.insert:con="+constituent+" sp["+pi+"] "+subPart+" "+subPart.getType());
        Span sp = subPart.span;
        if (sp.getStart() >= ic.getEnd()) {
          break;
        }
        // constituent contains subPart
        else if (ic.contains(sp)) {
          //System.err.println("Parse.insert:con contains subPart");
          parts.remove(pi);
          pi--;
          constituent.parts.add(subPart);
          subPart.setParent(constituent);
          //System.err.println("Parse.insert: "+subPart.hashCode()+" -> "+subPart.getParent().hashCode());
          pn = parts.size();
        }
        else if (sp.contains(ic)) {
          //System.err.println("Parse.insert:subPart contains con");
          subPart.insert(constituent);
          return;
        }
      }
      //System.err.println("Parse.insert:adding con="+constituent+" to "+this);
      parts.add(pi, constituent);
      constituent.setParent(this);
      //System.err.println("Parse.insert: "+constituent.hashCode()+" -> "+constituent.getParent().hashCode());
    }
    else {
      throw new IllegalArgumentException("Inserting constituent not contained in the sentence!");
    }
  }

  /**
   * Appends the specified string buffer with a string representation of this parse.
   *
   * @param sb A string buffer into which the parse string can be appended.
   */
  public void show(StringBuffer sb) {
    int start;
    start = span.getStart();
    if (!type.equals(AbstractBottomUpParser.TOK_NODE)) {
      sb.append("(");
      sb.append(type).append(" ");
      //System.out.print(label+" ");
      //System.out.print(head+" ");
      //System.out.print(df.format(prob)+" ");
    }
    for (Iterator i = parts.iterator(); i.hasNext();) {
      Parse c = i.next();
      Span s = c.span;
      if (start < s.getStart()) {
        //System.out.println("pre "+start+" "+s.getStart());
        sb.append(encodeToken(text.substring(start, s.getStart())));
      }
      c.show(sb);
      start = s.getEnd();
    }
    if (start < span.getEnd()) {
      sb.append(encodeToken(text.substring(start, span.getEnd())));
    }
    if (!type.equals(AbstractBottomUpParser.TOK_NODE)) {
      sb.append(")");
    }
  }

  /**
   * Displays this parse using Penn Treebank-style formatting.
   */
  public void show() {
    StringBuffer sb = new StringBuffer(text.length()*4);
    show(sb);
    System.out.println(sb);
  }


  /**
   * Returns the probability associated with the pos-tag sequence assigned to this parse.
   *
   * @return The probability associated with the pos-tag sequence assigned to this parse.
   */
  public double getTagSequenceProb() {
    //System.err.println("Parse.getTagSequenceProb: "+type+" "+this);
    if (parts.size() == 1 && (parts.get(0)).type.equals(AbstractBottomUpParser.TOK_NODE)) {
      //System.err.println(this+" "+prob);
      return (Math.log(prob));
    }
    else if (parts.size() == 0) {
      System.err.println("Parse.getTagSequenceProb: Wrong base case!");
      return (0.0);
    }
    else {
      double sum = 0.0;
      for (Iterator pi = parts.iterator(); pi.hasNext();) {
        sum += pi.next().getTagSequenceProb();
      }
      return sum;
    }
  }

  /**
   * Returns whether this parse is complete.
   *
   * @return Returns true if the parse contains a single top-most node.
   */
  public boolean complete() {
    return (parts.size() == 1);
  }

  public String getCoveredText() {
    return text.substring(span.getStart(), span.getEnd());
  }

  /**
   * Represents this parse in a human readable way.
   */
  @Override
  public String toString() {
    // TODO: Use the commented code in next bigger release,
    // change probably breaks backward compatibility in some
    // applications
    //StringBuffer buffer = new StringBuffer();
    //show(buffer);
    //return buffer.toString();
    return getCoveredText();
  }

  /**
   * Returns the text of the sentence over which this parse was formed.
   *
   * @return The text of the sentence over which this parse was formed.
   */
  public String getText() {
    return text;
  }

  /**
   * Returns the character offsets for this constituent.
   *
   * @return The character offsets for this constituent.
   */
  public Span getSpan() {
    return span;
  }

  /**
   * Returns the log of the product of the probability associated with all the decisions which formed this constituent.
   *
   * @return The log of the product of the probability associated with all the decisions which formed this constituent.
   */
  public double getProb() {
    return prob;
  }

  /**
   * Adds the specified probability log to this current log for this parse.
   *
   * @param logProb The probability of an action performed on this parse.
   */
  public void addProb(double logProb) {
    this.prob+=logProb;
  }

  /**
   * Returns the child constituents of this constituent
   * .
   * @return The child constituents of this constituent.
   */
  public Parse[] getChildren() {
    return parts.toArray(new Parse[parts.size()]);
  }

  /**
   * Replaces the child at the specified index with a new child with the specified label.
   *
   * @param index The index of the child to be replaced.
   * @param label The label to be assigned to the new child.
   */
  public void setChild(int index, String label) {
    Parse newChild = (Parse) (parts.get(index)).clone();
    newChild.setLabel(label);
    parts.set(index,newChild);
  }

  public void add(Parse daughter, HeadRules rules) {
    if (daughter.prevPunctSet != null) {
      parts.addAll(daughter.prevPunctSet);
    }
    parts.add(daughter);
    this.span = new Span(span.getStart(),daughter.getSpan().getEnd());
    this.head = rules.getHead(getChildren(),type);
    if (head == null) {
      System.err.println(parts);
    }
    this.headIndex = head.headIndex;
  }

  public void remove(int index) {
    parts.remove(index);
    if(! parts.isEmpty()) {
        if (index == 0 || index == parts.size()) { //size is orig last element
            span = new Span((parts.get(0)).span.getStart(),(parts.get(parts.size()-1)).span.getEnd());
          }
    }
  }

  public Parse adjoinRoot(Parse node, HeadRules rules, int parseIndex) {
    Parse lastChild = parts.get(parseIndex);
    Parse adjNode = new Parse(this.text,new Span(lastChild.getSpan().getStart(),node.getSpan().getEnd()),lastChild.getType(),1,rules.getHead(new Parse[]{lastChild,node},lastChild.getType()));
    adjNode.parts.add(lastChild);
    if (node.prevPunctSet != null) {
      adjNode.parts.addAll(node.prevPunctSet);
    }
    adjNode.parts.add(node);
    parts.set(parseIndex,adjNode);
    return adjNode;
  }

  /**
   * Sister adjoins this node's last child and the specified sister node and returns their
   * new parent node.  The new parent node replace this nodes last child.
   *
   * @param sister The node to be adjoined.
   * @param rules The head rules for the parser.
   * @return The new parent node of this node and the specified sister node.
   */
  public Parse adjoin(Parse sister, HeadRules rules) {
    Parse lastChild = parts.get(parts.size()-1);
    Parse adjNode = new Parse(this.text,new Span(lastChild.getSpan().getStart(),sister.getSpan().getEnd()),lastChild.getType(),1,rules.getHead(new Parse[]{lastChild,sister},lastChild.getType()));
    adjNode.parts.add(lastChild);
    if (sister.prevPunctSet != null) {
      adjNode.parts.addAll(sister.prevPunctSet);
    }
    adjNode.parts.add(sister);
    parts.set(parts.size()-1,adjNode);
    this.span = new Span(span.getStart(),sister.getSpan().getEnd());
    this.head = rules.getHead(getChildren(),type);
    this.headIndex = head.headIndex;
    return adjNode;
  }

  public void expandTopNode(Parse root) {
    boolean beforeRoot = true;
    //System.err.println("expandTopNode: parts="+parts);
    for (int pi=0,ai=0;pi nodes = new LinkedList();
    nodes.add(parse);
    while(nodes.size() != 0) {
      Parse node = nodes.remove(0);
      Parse[] children = node.getChildren();
      if (children.length == 1 && node.getType().equals(children[0].getType())) {
        int index = node.getParent().parts.indexOf(node);
        children[0].setParent(node.getParent());
        node.getParent().parts.set(index,children[0]);
        node.parent = null;
        node.parts = null;
      }
      nodes.addAll(Arrays.asList(children));
    }
  }

  public static void fixPossesives(Parse parse) {
    Parse[] tags = parse.getTagNodes();
    for (int ti=0;ti stack = new Stack<>();
    List cons = new LinkedList<>();
    for (int ci = 0, cl = parse.length(); ci < cl; ci++) {
      char c = parse.charAt(ci);
      if (c == '(') {
        String rest = parse.substring(ci + 1);
        String type = getType(rest);
        if (type == null) {
          System.err.println("null type for: " + rest);
        }
        String token = getToken(rest);
        stack.push(new Constituent(type, new Span(offset,offset)));
        if (token != null) {
          if (Objects.equals(type, "-NONE-") && gl != null) {
            //System.err.println("stack.size="+stack.size());
            gl.labelGaps(stack);
          }
          else {
            cons.add(new Constituent(AbstractBottomUpParser.TOK_NODE,
                new Span(offset, offset + token.length())));
            text.append(token).append(" ");
            offset += token.length() + 1;
          }
        }
      }
      else if (c == ')') {
        Constituent con = stack.pop();
        int start = con.getSpan().getStart();
        if (start < offset) {
          cons.add(new Constituent(con.getLabel(), new Span(start, offset-1)));
        }
      }
    }
    String txt = text.toString();
    int tokenIndex = -1;
    Parse p = new Parse(txt, new Span(0, txt.length()), AbstractBottomUpParser.TOP_NODE, 1,0);
    for (int ci=0;ci < cons.size();ci++) {
      Constituent con = cons.get(ci);
      String type = con.getLabel();
      if (!type.equals(AbstractBottomUpParser.TOP_NODE)) {
        if (AbstractBottomUpParser.TOK_NODE.equals(type)) {
          tokenIndex++;
        }
        Parse c = new Parse(txt, con.getSpan(), type, 1,tokenIndex);
        //System.err.println("insert["+ci+"] "+type+" "+c.toString()+" "+c.hashCode());
        p.insert(c);
        //codeTree(p);
      }
    }
    return p;
  }

  /**
   * Returns the parent parse node of this constituent.
   *
   * @return The parent parse node of this constituent.
   */
  public Parse getParent() {
    return parent;
  }

  /**
   * Specifies the parent parse node for this constituent.
   *
   * @param parent The parent parse node for this constituent.
   */
  public void setParent(Parse parent) {
    this.parent = parent;
  }

  /**
   * Indicates whether this parse node is a pos-tag.
   *
   * @return true if this node is a pos-tag, false otherwise.
   */
  public boolean isPosTag() {
    return (parts.size() == 1 &&
        (parts.get(0)).getType().equals(AbstractBottomUpParser.TOK_NODE));
  }

  /**
   * Returns true if this constituent contains no sub-constituents.
   *
   * @return true if this constituent contains no sub-constituents; false otherwise.
   */
  public boolean isFlat() {
    boolean flat = true;
    for (int ci=0;ci tags = new LinkedList<>();
    List nodes = new LinkedList<>();
    nodes.addAll(this.parts);
    while(nodes.size() != 0) {
      Parse p = nodes.remove(0);
      if (p.isPosTag()) {
        tags.add(p);
      }
      else {
        nodes.addAll(0,p.parts);
      }
    }
    return tags.toArray(new Parse[tags.size()]);
  }

  /**
   * Returns the deepest shared parent of this node and the specified node.
   * If the nodes are identical then their parent is returned.
   * If one node is the parent of the other then the parent node is returned.
   *
   * @param node The node from which parents are compared to this node's parents.
   *
   * @return the deepest shared parent of this node and the specified node.
   */
  public Parse getCommonParent(Parse node) {
    if (this == node) {
      return parent;
    }
    Set parents = new HashSet<>();
    Parse cparent = this;
    while(cparent != null) {
      parents.add(cparent);
      cparent = cparent.getParent();
    }
    while (node != null) {
      if (parents.contains(node)) {
        return node;
      }
      node = node.getParent();
    }
    return null;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof Parse) {
      Parse p = (Parse) o;
      if (this.label == null) {
        if (p.label != null) {
          return false;
        }
      }
      else if (!this.label.equals(p.label)) {
        return false;
      }
      if (!this.span.equals(p.span)) {
        return false;
      }
      if (!this.text.equals(p.text)) {
        return false;
      }
      if (this.parts.size() != p.parts.size()){
      	return false;
      }
      for (int ci=0;ci p.getProb()) {
      return -1;
    }
    else if (this.getProb() < p.getProb()) {
      return 1;
    }
    return 0;
  }

  /**
   * Returns the derivation string for this parse if one has been created.
   *
   * @return the derivation string for this parse or null if no derivation
   * string has been created.
   */
  public StringBuffer getDerivation() {
    return derivation;
  }

  /**
   * Specifies the derivation string to be associated with this parse.
   *
   * @param derivation The derivation string to be associated with this parse.
   */
  public void setDerivation(StringBuffer derivation) {
    this.derivation = derivation;
  }

  private void codeTree(Parse p,int[] levels) {
    Parse[] kids = p.getChildren();
    StringBuilder levelsBuff = new StringBuilder();
    levelsBuff.append("[");
    int[] nlevels = new int[levels.length+1];
    for (int li=0;li " + kids[ki].getParent().hashCode() +
          " " + kids[ki].getParent().getType() + " " + kids[ki].getCoveredText());
      codeTree(kids[ki],nlevels);
    }
  }

  /**
   * Prints to standard out a representation of the specified parse which
   * contains hash codes so that parent/child relationships can be explicitly seen.
   */
  public void showCodeTree() {
    codeTree(this,new int[0]);
  }

  /**
   * Utility method to inserts named entities.
   *
   * @param tag
   * @param names
   * @param tokens
   */
  public static void addNames(String tag, Span[] names, Parse[] tokens) {
    for (int ni=0,nn=names.length;ni 1 && nameSpan.contains(grandKids[grandKids.length-1].getSpan())) {
                commonParent.insert(new Parse(commonParent.getText(),commonParent.getSpan(),tag,1.0,commonParent.getHeadIndex()));
              }
            }
          }
        }
      }
    }
  }

  /**
   * Reads training parses (one-sentence-per-line) and displays parse structure.
   *
   * @param args The head rules files.
   *
   * @throws IOException If the head rules file can not be opened and read.
   */
  @Deprecated
  public static void main(String[] args) throws java.io.IOException {
    if (args.length == 0) {
      System.err.println("Usage: Parse -fun -pos head_rules < train_parses");
      System.err.println("Reads training parses (one-sentence-per-line) and displays parse structure.");
      System.exit(1);
    }
    int ai=0;
    boolean fixPossesives = false;
    while(args[ai].startsWith("-") && ai < args.length) {
      if (args[ai].equals("-fun")) {
        Parse.useFunctionTags(true);
        ai++;
      }
      else if (args[ai].equals("-pos")) {
        fixPossesives = true;
        ai++;
      }
    }

    opennlp.tools.parser.lang.en.HeadRules rules = new opennlp.tools.parser.lang.en.HeadRules(args[ai]);
    java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));

    for (String line = in.readLine(); line != null; line = in.readLine()) {
      Parse p = Parse.parseParse(line,rules);
      Parse.pruneParse(p);
      if (fixPossesives) {
        Parse.fixPossesives(p);
      }
      p.updateHeads(rules);
      p.show();
      //p.showCodeTree();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy