POSTagger.java
001 /*
002  *  POSTagger.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  HepTag was originally written by Mark Hepple, this version contains
013  *  modifications by Valentin Tablan and Niraj Aswani.
014  *
015  *  $Id: POSTagger.java 17605 2014-03-09 10:15:34Z markagreenwood $
016  */
017 
018 /*
019  * INSTRUCTIONS for STAND-ALONE USE
020  *
021  * SYNOPSIS
022  *     java hepple.postag.POSTagger [options] file1 [file2 ...]
023  * OPTIONS:
024  *     -h, --help : displays this message
025  *     -l, --lexicon <lexicon file> : uses specified lexicon
026  *     -r, --rules <rules file> : uses specified rules
027  *
028  * NOTE: requires gnu.getopt package
029  */
030 
031 /**
032  * Title:        HepTag
033  * Description:  Mark Hepple's POS tagger
034  * Copyright:    Copyright (c) 2001
035  * Company:      University of Sheffield
036  @author Mark Hepple
037  @version 1.0
038  */
039 package hepple.postag;
040 
041 
042 import gate.util.BomStrippingInputStreamReader;
043 import gnu.getopt.Getopt;
044 import gnu.getopt.LongOpt;
045 
046 import java.io.BufferedReader;
047 import java.io.File;
048 import java.io.FileReader;
049 import java.io.IOException;
050 import java.net.URL;
051 import java.util.ArrayList;
052 import java.util.HashMap;
053 import java.util.Iterator;
054 import java.util.List;
055 import java.util.Map;
056 import java.util.StringTokenizer;
057 
058 import org.apache.commons.io.IOUtils;
059 
060 /**
061  * A Java POS Tagger
062  *
063  * Author: Mark Hepple (hepple@dcs.shef.ac.uk)
064  *
065  * Input:  An ascii text file in "Brill input format", i.e. one
066  *        sentence per line, tokens separated by spaces.
067  *
068  * Output: Same text with each token tagged, i.e. "token" -> "token/tag".
069  *        Output is just streamed to std-output, so commonly will direct
070  *        into some target file.
071  *
072  * Revision: 13/9/00. Version 1.0.
073  *
074  * Comments:
075  *
076  * Implements a version of the decision list based tagging method
077  * described in:
078  *
079  * M. Hepple. 2000. Independence and Commitment: Assumptions for Rapid
080  * Training and Execution of Rule-based Part-of-Speech Taggers.
081  * Proceedings of the 38th Annual Meeting of the Association for
082  * Computational Linguistics (ACL-2000). Hong Kong, October 2000.
083  *
084  * Modified by Niraj Aswani/Ian Roberts to allow explicit specification of the
085  * character encoding to use when reading rules and lexicon files.
086  *
087  * $Id: POSTagger.java 17605 2014-03-09 10:15:34Z markagreenwood $
088  *
089  */
090 
091 public class POSTagger {
092 
093 //    static final int MAXTAGS = 200;
094 
095     protected Map<String, List<Rule>> rules;
096 //    public Rule[] rules = new Rule[MAXTAGS];
097 //    public Rule[] lastRules = new Rule[MAXTAGS];
098 
099 
100     Lexicon lexicon;
101 
102     private String encoding;
103 
104     static final String staart = "STAART";
105 
106     private String[] staartLex = staart };
107     private String[] deflex_NNP = "NNP"};
108     private String[] deflex_JJ  = "JJ"};
109     private String[] deflex_CD  = "CD"};
110     private String[] deflex_NNS = "NNS"};
111     private String[] deflex_RB  = "RB"};
112     private String[] deflex_VBG = "VBG"};
113     private String[] deflex_NN  = "NN"};
114 
115     public String[] wordBuff  = staart,staart,staart,staart,
116         staart,staart,staart };
117 
118     public String[] tagBuff   = staart,staart,staart,staart,
119         staart,staart,staart };
120     public String[][] lexBuff = staartLex,staartLex,staartLex,
121          staartLex,staartLex,staartLex,
122          staartLex };
123 
124     /**
125      * Construct a POS tagger using the platform's native encoding to read the
126      * lexicon and rules files.
127      */
128     public POSTagger(URL lexiconURL, URL rulesURLthrows InvalidRuleException,
129                                                           IOException {
130       this(lexiconURL, rulesURL, null);
131     }
132 
133     /**
134      * Construct a POS tagger using the specified encoding to read the lexicon
135      * and rules files.
136      */
137     public POSTagger(URL lexiconURL, URL rulesURL, String encodingthrows InvalidRuleException,
138                                                           IOException{
139       this.encoding = encoding;
140       this.lexicon = new Lexicon(lexiconURL, encoding);
141       rules = new HashMap<String, List<Rule>>();
142       readRules(rulesURL);
143     }
144 
145   /**
146    * Creates a new rule of the required type according to the provided ID.
147    @param ruleId the ID for the rule to be created
148    */
149   public Rule createNewRule(String ruleIdthrows InvalidRuleException{
150     try{
151       String className = "hepple.postag.rules.Rule_" + ruleId;
152       Class<?> ruleClass = Class.forName(className);
153       return (Rule)ruleClass.newInstance();
154     }catch(Exception e){
155       throw new InvalidRuleException("Could not create rule " + ruleId + "!\n" +
156                                      e.toString());
157     }
158   }
159 
160   /**
161    * Runs the tagger over a set of sentences.
162    @param sentences a {@link java.util.List} of {@link java.util.List}s
163    * of words to be tagged. Each list is a sentence represented as a list of
164    * words.
165    @return {@link java.util.List} of {@link java.util.List}s of
166    {@link java.lang.String}[]. A list of tagged sentences, each sentence
167    * being itself a list having pairs of strings as elements with
168    * the word on the first position and the tag on the second.
169    */
170   public List<List<String[]>> runTagger(List<List<String>> sentences){
171     List<List<String[]>> output = new ArrayList<List<String[]>>();
172     List<String[]> taggedSentence = new ArrayList<String[]>();
173     Iterator<List<String>> sentencesIter = sentences.iterator();
174     while(sentencesIter.hasNext()){
175       List<String> sentence = sentencesIter.next();
176       Iterator<String> wordsIter = sentence.iterator();
177       while(wordsIter.hasNext()){
178         String newWord = wordsIter.next();
179         oneStep(newWord, taggedSentence);
180       }//while(wordsIter.hasNext())
181       //finished adding all the words from a sentence, add six more
182       //staarts to flush all words out of the tagging buffer
183       for(int i = 0; i < 6; i++){
184         oneStep(staart, taggedSentence);
185       }
186       //we have a new finished sentence
187       output.add(taggedSentence);
188       taggedSentence = new ArrayList<String[]>();
189     }//while(sentencesIter.hasNext())
190     return output;
191   }
192 
193   /**
194    * Adds a new word to the window of 7 words (on the last position) and tags
195    * the word currently in the middle (i.e. on position 3). This function
196    * also reads the word on the first position and adds its tag to the
197    * taggedSentence structure as this word would be lost at the next advance.
198    * If this word completes a sentence then it returns true otherwise it
199    * returns false.
200    @param word the new word
201    @param taggedSentence a List of pairs of strings representing the results
202    * of tagging the current sentence so far.
203    @return returns true if a full sentence is now tagged, otherwise false.
204    */
205   protected boolean oneStep(String word, List<String[]> taggedSentence){
206     //add the new word at the end of the text window
207     for (int i=; i<; i++) {
208       wordBuff[i-1= wordBuff[i];
209       tagBuff[i-1= tagBuff[i];
210       lexBuff[i-1= lexBuff[i];
211     }
212     wordBuff[6= word;
213     lexBuff[6= classifyWord(word);
214     tagBuff[6= lexBuff[6][0];
215 
216     //apply the rules to the word in the middle of the text window
217     //Try to fire a rule for the current lexical entry. It may be the case that
218     //no rule applies.
219     List<Rule> rulesToApply = rules.get(lexBuff[3][0]);
220     if(rulesToApply != null && rulesToApply.size() 0){
221       Iterator<Rule> rulesIter = rulesToApply.iterator();
222       //find the first rule that applies, fire it and stop.
223       while(rulesIter.hasNext() && !(rulesIter.next()).apply(this)){}
224     }
225 
226     //save the tagged word from the first position
227     String taggedWord = wordBuff[0];
228     if(taggedWord != staart){
229       taggedSentence.add(new String[]{taggedWord, tagBuff[0]});
230       if(wordBuff[1== staart){
231         //wordTag[0] was the end of a sentence
232         return true;
233       }//if(wordBuff[1] == staart)
234     }//if(taggedWord != staart)
235     return false;
236 
237   }//protected List oneStep(String word, List taggedSentence)
238 
239   /**
240    * Reads the rules from the rules input file
241    */
242   @SuppressWarnings("resource")
243   public void readRules(URL rulesURLthrows IOException, InvalidRuleException{
244     BufferedReader rulesReader = null;
245     
246     try {
247       if(encoding == null) {
248         rulesReader = new BomStrippingInputStreamReader(rulesURL.openStream());
249       else {
250         rulesReader = new BomStrippingInputStreamReader(rulesURL.openStream()this.encoding);
251       }
252   
253       String line;
254       Rule newRule;
255   
256       line = rulesReader.readLine();
257       while(line != null){
258         List<String> ruleParts = new ArrayList<String>();
259         StringTokenizer tokens = new StringTokenizer(line);
260         while (tokens.hasMoreTokens()) ruleParts.add(tokens.nextToken());
261         if (ruleParts.size() 3throw new InvalidRuleException(line);
262   
263         newRule = createNewRule(ruleParts.get(2));
264         newRule.initialise(ruleParts);
265         List<Rule> existingRules = rules.get(newRule.from);
266         if(existingRules == null){
267           existingRules = new ArrayList<Rule>();
268           rules.put(newRule.from, existingRules);
269         }
270         existingRules.add(newRule);
271   
272         line = rulesReader.readLine();
273       }//while(line != null)
274     }
275     finally {
276       IOUtils.closeQuietly(rulesReader);
277     }
278   }//public void readRules()
279 
280   public void showRules(){
281     System.out.println(rules);
282   }
283 
284   /**
285    * Attempts to classify an unknown word.
286    @param wd the word to be classified
287    */
288   protected String[] classifyWord(String wd){
289     String[] result;
290 
291     if (staart.equals(wd)) return staartLex;
292 
293     List<String> categories = lexicon.get(wd);
294     if(categories != null){
295       result = new String[categories.size()];
296       for(int i = 0; i < result.length; i++){
297         result[i= categories.get(i);
298       }
299       return result;
300     }
301 
302     //no lexical entry for the word. Try to guess
303     if ('A' <= wd.charAt(0&& wd.charAt(0<= 'Z'return deflex_NNP;
304 
305     for (int i=; i < wd.length()-; i++)
306       if (wd.charAt(i== '-'return deflex_JJ;
307 
308     for (int i=; i < wd.length() ; i++)
309       if ('0' <= wd.charAt(i&& wd.charAt(i<= '9'return deflex_CD;
310 
311     if (wd.endsWith("ed"||
312         wd.endsWith("us"||
313         wd.endsWith("ic"||
314         wd.endsWith("ble"||
315         wd.endsWith("ive"||
316         wd.endsWith("ary"||
317         wd.endsWith("ful"||
318         wd.endsWith("ical"||
319         wd.endsWith("less")) return deflex_JJ;
320 
321     if (wd.endsWith("s")) return deflex_NNS;
322 
323     if (wd.endsWith("ly")) return deflex_RB;
324 
325     if (wd.endsWith("ing")) return deflex_VBG;
326 
327     return deflex_NN;
328   }//private String[] classifyWord(String wd)
329 
330 
331   /**
332    * Main method. Runs the tagger using the arguments to find the resources
333    * to be used for initialisation and the input file.
334    */
335   public static void main(String[] args){
336     if(args.length == 0help();
337     try{
338       LongOpt[] options = new LongOpt[]{
339         new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'),
340         new LongOpt("lexicon", LongOpt.NO_ARGUMENT, null, 'l'),
341         new LongOpt("rules", LongOpt.NO_ARGUMENT, null, 'r')
342       };
343       Getopt getopt = new Getopt("HepTag", args, "hl:r:", options);
344       String lexiconUrlString = null;
345       String rulesUrlString = null;
346       int opt;
347       while( (opt = getopt.getopt()) != -){
348         switch(opt) {
349           // -h
350           case 'h':{
351             help();
352             System.exit(0);
353             break;
354           }
355           // -l new lexicon
356           case 'l':{
357             lexiconUrlString = getopt.getOptarg();
358             break;
359           }
360           // -l new lexicon
361           case 'r':{
362             rulesUrlString = getopt.getOptarg();
363             break;
364           }
365           default:{
366             System.err.println("Invalid option " +
367                                args[getopt.getOptind() -1"!");
368             System.exit(1);
369           }
370         }//switch(opt)
371       }//while( (opt = g.getopt()) != -1 )
372       String[] fileNames = new String[args.length - getopt.getOptind()];
373       for(int i = getopt.getOptind(); i < args.length; i++){
374        fileNames[i - getopt.getOptind()] = args[i];
375       }
376 
377       URL lexiconURL = (lexiconUrlString == null?
378                        POSTagger.class.
379                        getResource("/hepple/resources/sample_lexicon":
380                        new File(lexiconUrlString).toURI().toURL();
381 
382       URL rulesURL = (rulesUrlString == null?
383                        POSTagger.class.
384                        getResource("/hepple/resources/sample_ruleset.big":
385                        new File(rulesUrlString).toURI().toURL();
386 
387       POSTagger tagger = new POSTagger(lexiconURL, rulesURL);
388 
389       for(int i = 0; i < fileNames.length; i++){
390         String file = fileNames[i];
391         BufferedReader reader = null;
392         
393         try {
394           reader = new BufferedReader(new FileReader(file));
395         
396           String line = reader.readLine();
397   
398           while(line != null){
399             StringTokenizer tokens = new StringTokenizer(line);
400             List<String> sentence = new ArrayList<String>();
401             while(tokens.hasMoreTokens()) sentence.add(tokens.nextToken());
402             List<List<String>> sentences = new ArrayList<List<String>>();
403             sentences.add(sentence);
404             List<List<String[]>> result = tagger.runTagger(sentences);
405   
406             Iterator<List<String[]>> iter = result.iterator();
407             while(iter.hasNext()){
408               List<String[]> sentenceFromTagger = iter.next();
409               Iterator<String[]> sentIter = sentenceFromTagger.iterator();
410               while(sentIter.hasNext()){
411                 String[] tag = sentIter.next();
412                 System.out.print(tag[0"/" + tag[1]);
413                 if(sentIter.hasNext()) System.out.print(" ");
414                 else System.out.println();
415               }//while(sentIter.hasNext())
416             }//while(iter.hasNext())
417             line = reader.readLine();
418           }//while(line != null)
419         }
420         finally {
421           IOUtils.closeQuietly(reader);
422         }
423 //
424 //
425 //
426 //        List result = tagger.runTagger(readInput(file));
427 //        Iterator iter = result.iterator();
428 //        while(iter.hasNext()){
429 //          List sentence = (List)iter.next();
430 //          Iterator sentIter = sentence.iterator();
431 //          while(sentIter.hasNext()){
432 //            String[] tag = (String[])sentIter.next();
433 //            System.out.print(tag[0] + "/" + tag[1]);
434 //            if(sentIter.hasNext()) System.out.print(" ");
435 //            else System.out.println();
436 //          }//while(sentIter.hasNext())
437 //        }//while(iter.hasNext())
438       }//for(int i = 0; i < fileNames.length; i++)
439     }catch(Exception e){
440       e.printStackTrace();
441     }
442   }//public static void main(String[] args)
443 
444   /**
445    * Prints the help message
446    */
447   private static void help(){
448     System.out.println(
449       "NAME\n" +
450       "HepTag - a Part-of-Speech tagger\n" +
451       "see http://www.dcs.shef.ac.uk/~hepple/papers/acl00/abstract.html \n\n" +
452       "SYNOPSIS\n\tjava hepple.postag.POSTagger [options] file1 [file2 ...]\n\n" +
453       "OPTIONS:\n" +
454       "-h, --help \n\tdisplays this message\n" +
455       "-l, --lexicon <lexicon file>\n\tuses specified lexicon\n" +
456       "-r, --rules <rules file>\n\tuses specified rules");
457   }
458 
459   /**
460    * Reads one input file and creates the structure needed by the tagger
461    * for input.
462    */
463   @SuppressWarnings("unused")
464   private static List<List<String>> readInput(String filethrows IOException{
465     BufferedReader reader = null;
466     
467     try {
468       reader = new BufferedReader(new FileReader(file));
469     
470       String line = reader.readLine();
471       List<List<String>> result = new ArrayList<List<String>>();
472       while(line != null){
473         StringTokenizer tokens = new StringTokenizer(line);
474         List<String> sentence = new ArrayList<String>();
475         while(tokens.hasMoreTokens()) sentence.add(tokens.nextToken());
476         result.add(sentence);
477         line = reader.readLine();
478       }//while(line != null)
479       return result;
480     }
481     finally {
482       IOUtils.closeQuietly(reader);
483     }
484   }//private static List readInput(File file) throws IOException
485 
486 }//public class POSTagger