/*
* 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 org.apache.uima.conceptMapper.support.tokenizer;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.uima.analysis_engine.ResultSpecification;
import org.apache.uima.analysis_engine.annotator.AnnotatorConfigurationException;
import org.apache.uima.analysis_engine.annotator.AnnotatorContext;
import org.apache.uima.analysis_engine.annotator.AnnotatorInitializationException;
import org.apache.uima.analysis_engine.annotator.AnnotatorProcessException;
import org.apache.uima.analysis_engine.annotator.JTextAnnotator_ImplBase;
import org.apache.uima.conceptMapper.support.stemmer.Stemmer;
import org.apache.uima.jcas.JCas;
/**
* Simple class to tokenize a string (similar to java.util.StringTokenizer), except
* that this tokenizer returns
* {@link org.apache.uima.conceptMapper.support.TokenAnnotation TokenAnnotation} objects, which, in
* addition to the token text string, also contain the start and end offsets of the token in the
* original string.
*
*
* The tokenizer will optionally perform stemming and case normalization on the tokens, and the set
* of characters that delimit tokens may be specified. The default stemmer is the Snowball Porter
* stemmer, but any stemmer may be supplied to the tokenizer as long as it implements the
* {@link org.apache.uima.conceptMapper.support.Stemmer Stemmer}interface.
*
*/
public class OffsetTokenizer extends JTextAnnotator_ImplBase {
/** The input text string being tokenized */
private String text;
/** The stemmer that will perform the stemming. */
private Stemmer stemmer = null;
//private Logger logger;
/** Configuration parameter key/label for the case matching string */
public static final String PARAM_CASE_MATCH = "caseMatch";
/** Configuration parameter key/label for the stemmer class spec */
public static final String PARAM_STEMMER_CLASS = "Stemmer";
/** Configuration parameter key/label for the token delimiters string */
public static final String PARAM_TOKEN_DELIM = "tokenDelimiters";
/** The current offset into {@link TextToken.text} */
private int offset;
/** The total length of {@link TextToken.text} */
private int length;
/** Base whitespace token delimiters */
private String wsDelim = " \t\n\r\f";
/**
* The extra specifiable delimiters used by the tokenizer to separate the input string into
* individual tokens.
*/
private String extraDelim = ",-/();:";
/**
* The delimiters used by the tokenizer to separate the input string into individual tokens.
*/
private String delim = wsDelim + extraDelim;
/** Case folding flag for folding tokens with initial cap. */
private boolean caseFoldInitCap = false;
/** Case folding flag for folding tokens with at least one digit character. */
private boolean caseFoldDigit = false;
/**
* Case folding flag for folding all tokens (subsumes {@link #caseFoldInitCap caseFoldInitCap}and
* {@link #caseFoldDigit caseFoldDigit}).
*/
private boolean caseFoldAll = false;
/** Stemming on/off flag. If true, tokens will be stemmed by the tokenizer. */
private boolean stemTokens = false;
/**
* regular expression pattern used to identify terms with an initial capital letter followed by
* all lowercase letters.
*/
private Pattern capPat = null;
/**
* regular expression pattern used to identify terms with at least one digit character in them.
*/
private Pattern hasDigit = null;
/**
* Create a new OffsetTokenizer. Initializes the default stemmer and sets up the
* regular expressions for the various case folding options.
*/
public OffsetTokenizer() {
try {
capPat = Pattern.compile("^[A-Z][a-z]+$");
hasDigit = Pattern.compile("[0-9]");
} catch (PatternSyntaxException e) {
e.printStackTrace();
}
}
/**
* @return Returns the text.
*/
public String getText() {
return text;
}
/**
* Set the text to tokenize. After this method is called, the next call to
* {@link #nextToken nextToken} will return the first token from the input string
* {@link TextToken.text}
*/
public void setText(String text) {
this.text = text;
offset = 0;
length = getText().length();
}
/**
* @return Returns the stemmer.
*/
public Stemmer getStemmer() {
return stemmer;
}
/**
* @param stemmer
* The stemmer to set.
*/
public void setStemmer(Stemmer stemmer) {
this.stemmer = stemmer;
}
public TokenAnnotation newToken(JCas jcas) {
return new TokenAnnotation(jcas);
}
public TokenAnnotation nextToken(JCas jcas) {
StringBuffer token = new StringBuffer();
// System.out.println("in NextToken");
// skip any delimiters
while ((offset < length) && (getDelim().indexOf(getText().charAt((offset))) >= 0)) {
offset += 1;
}
if (offset < length) {
int start = offset;
while ((offset < length) && (getDelim().indexOf(getText().charAt((offset))) < 0)) {
token.append(getText().charAt(offset));
offset += 1;
}
TokenAnnotation returnVal = newToken(jcas);
// System.out.println("token = " + token.toString() + " fold = " +
// foldCase(token.toString()));
returnVal.setText(stem(foldCase(token.toString())));
returnVal.setBegin(start);
returnVal.setEnd(offset);
return returnVal;
}
return null;
}
/**
* If one of the case folding flags is true and the input string matches the character pattern
* corresponding to that flag, then convert all letters to lowercase.
*
* @param token
* The string to case fold
*
* @return The case folded string
*/
protected String foldCase(String token) {
if (shouldFoldCase(token)) {
return doFoldCase(token);
}
return token;
}
public static String doFoldCase(String token) {
return token.trim().toLowerCase();
}
public boolean shouldFoldCase(String token) {
return (caseFoldAll || (caseFoldInitCap && capPat.matcher(token).matches()) || (caseFoldDigit && hasDigit
.matcher(token).find()));
}
public boolean shouldStem() {
return stemTokens;
}
/**
* Set the delimiters used to separate the input string into tokens. This adds the new delimiters
* to the base whitespace delimiters " \t\n\r\f".
*
* @param delim
* The new set of delimiters.
*/
protected void setDelim(String delim) {
this.delim = wsDelim + delim;
}
/**
* Set the delimiters used to separate the input string into tokens. This sets the delimiters to
* exactly the given set. The base whitespace delimiters are not included.
*
* @param delim
* The new set of delimiters.
*/
protected void overrideDelim(String delim) {
this.delim = delim;
}
/**
* Turn stemming on (true) or off (false).
*
* @param flag
* true - turn stemming on, false - turn stemming off
*/
private void setStemming(boolean flag) {
stemTokens = flag;
}
/**
* Set case folding flag for folding tokens with initial cap.
*
* @param flag
* true - turn on case folding, false - turn off case folding.
*/
private void setCaseFoldInitCap(boolean flag) {
caseFoldInitCap = flag;
}
/**
* Set case folding flag for folding tokens with at least one digit character.
*
* @param flag
* true - turn on case folding, false - turn off case folding.
*/
private void setCaseFoldDigit(boolean flag) {
caseFoldDigit = flag;
}
/**
* Set case folding flag for folding all tokens (subsumes
* {@link #setCaseFoldInitCap setCaseFoldInitCap}and {@link #setCaseFoldDigitsetCaseFoldDigit}).
*
* @param flag
* true - turn on case folding, false - turn off case folding.
*/
private void setCaseFoldAll(boolean flag) {
caseFoldAll = flag;
}
/**
* Get the current list of delimiters used to separate the input string into tokens.
*
* @return The current list of delimiters used to separate the input string into tokens.
*/
protected String getDelim() {
return delim;
}
/**
* Get the current stemming flag.
*
* @return true if stemming is currently on, false otherwise
*/
protected boolean getStemming() {
return stemTokens;
}
/**
* Get case folding flag for folding tokens with initial cap.
*
* @return the current value of the flag
*/
protected boolean getCaseFoldInitCap() {
return (caseFoldInitCap);
}
/**
* Get the case folding flag for folding tokens with at least one digit character.
*
* @return the current value of the flag
*/
protected boolean getCaseFoldDigit() {
return (caseFoldDigit);
}
/**
* Get case folding flag for folding all tokens.
*
* @return the current value of the flag.
*/
protected boolean getCaseFoldAll() {
return (caseFoldAll);
}
/**
* Initialize the annotator, which includes compilation of regular expressions, fetching
* configuration parameters from XML descriptor file, and loading of the dictionary file.
*/
public void initialize(AnnotatorContext annotatorContext)
throws AnnotatorInitializationException, AnnotatorConfigurationException {
try {
//logger = new Logger("TextTokenizer", annotatorContext.getLogger());
String[] configParameterNames = annotatorContext.getConfigParameterNames();
Object[] configParameters = new Object[configParameterNames.length];
for (int i = 0; i < configParameters.length; i++) {
configParameters[i] = annotatorContext.getConfigParameterValue(configParameterNames[i]);
}
processAllConfigurationParameters(configParameterNames, configParameters);
initTokenizer(configParameterNames, configParameters);
} catch (Exception e) {
throw new AnnotatorConfigurationException(e);
}
}
public void processAllConfigurationParameters(String[] configParameterNames,
Object[] configParameters) throws AnnotatorConfigurationException {
for (int i = 0; i < configParameterNames.length; i++) {
processConfigurationParameter(configParameterNames[i], configParameters[i]);
}
}
/**
* Perform the actual analysis. Iterate over the document content looking for tokens and post an
* annotation for each match found.
*
* @param aTCAS
* the current CAS to process.
* @param aResultSpec
* a specification of the result annotation that should be created by this annotator
*
* @see org.apache.uima.analysis_engine.annotator.JTextAnnotator#process(ResultSpecification)
*/
public void process(JCas jcas, ResultSpecification aResultSpec) throws AnnotatorProcessException {
try {
doTokenization(jcas, jcas.getDocumentText(), getDelim());
} catch (Exception e) {
throw new AnnotatorProcessException(e);
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.conceptMapper.support.tokenizer.TextTokenizer#initTokenizer(java.lang.String[],
* java.lang.Object[])
*/
public void initTokenizer(String[] paramNames, Object[] paramValues) throws Exception {
// nothing to do here
return;
}
/**
* @param jcas
* @param documentText
* @param delimiters
* @param logger
*/
protected void doTokenization(JCas jcas, String documentText, String delimiters) {
int numTokens = 0;
TokenAnnotation annotation;
// System.out.println("doTokenization = " + documentText);
overrideDelim(delimiters);
setText(documentText);
while (null != (annotation = nextToken(jcas))) {
annotation.addToIndexes();
numTokens++;
}
}
/**
* @param configParametersNames
* @param configParameters
*/
public void processConfigurationParameter(String configParameterName, Object configParameterValue) {
if (configParameterName.equals(PARAM_CASE_MATCH)) {
String caseSense = (String) configParameterValue;
if (caseSense != null) {
if (caseSense.equalsIgnoreCase("insensitive")) {
this.setCaseFoldInitCap(true);
} else if (caseSense.equalsIgnoreCase("digitfold")) {
this.setCaseFoldDigit(true);
} else if (caseSense.equalsIgnoreCase("ignoreall")) {
// System.out.println("SETCASEFOLDALL");
this.setCaseFoldAll(true);
}
}
} else if ((configParameterName.equals(PARAM_STEMMER_CLASS)) && (configParameterValue != null)) {
try {
// System.err.println ("Trying to instantiate stemmer class: '" +
// (String) configParameters [i] + "'");
Class> stemmerClass = Class.forName((String) configParameterValue);
setStemmer((Stemmer) stemmerClass.newInstance());
setStemming(true);
} catch (Exception e) {
System.err.println("Exception trying to instantiate stemmer class: '"
+ (String) configParameterValue + "', original exception:" + e.getMessage());
e.printStackTrace();
}
} else if (configParameterName.equals(PARAM_TOKEN_DELIM)) {
String tokenDelimiters = (String) configParameterValue;
if (tokenDelimiters != null) {
this.setDelim(tokenDelimiters);
}
}
}
/**
* If the stemming flag is true, then return the stemmed form of the supplied word using the
* Porter stemmer.
*
* @param token
* the word to stem
* @return the original word if the stemming flag is false, otherwise the stemmed form of the word
*/
protected String stem(String token) {
if (shouldStem()) {
return doStemming(token, getStemmer());
}
return token;
}
public static String doStemming(String token, Stemmer stemmer) {
return stemmer.stem(token.trim());
}
}