Created
April 4, 2014 22:10
-
-
Save i000313/9984096 to your computer and use it in GitHub Desktop.
This is a tokenizer based on spaces. #java #spacetokenizer #tokenizer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Iterator; | |
import java.util.regex.Pattern; | |
/** | |
* Classe que permite dividir um texto em tokens. A divisão é feita com | |
* base nos espaços encontrados no texto. O tokenizer tem 3 modos de funcionamento. | |
* Estes modos permitem indicar o que fazer com os símbolos (tudo que não seja uma | |
* letra nem um número) existentes no início e fim de cada token. | |
* | |
* De seguida é exibido um exemplo de como usar o Tokenizer no modo por omissão | |
* (modo {@link TokenizationMode#SEPARATE_PONCTUATION}): | |
* <pre> | |
* {@code | |
* Tokenizer tokenizer = new Tokenizer("Olá!! Isto «é» um teste..."); | |
* while (tokenizer.hasNext()) { | |
* System.out.println(tokenizer.next()); | |
* } | |
* | |
* // Irá imprimir: | |
* // Olá | |
* // !! | |
* // Isto | |
* // « | |
* // é | |
* // » | |
* // um | |
* // teste | |
* // ... | |
* } | |
* <pre> | |
* | |
* Exemplo da divisão do texto em modo {@link TokenizationMode#SIMPLE}: | |
* <pre> | |
* {@code | |
* Tokenizer tokenizer = new Tokenizer("Olá!! Isto «é» um teste...", TokenizationMode.SIMPLE); | |
* while (tokenizer.hasNext()) { | |
* System.out.println(tokenizer.next()); | |
* } | |
* | |
* // Irá imprimir: | |
* // Olá!! | |
* // Isto | |
* // «é» | |
* // um | |
* // teste... | |
* } | |
* <pre> | |
* | |
* Exemplo da divisão do texto em modo {@link TokenizationMode#DELETE_PONTUATION}: | |
* <pre> | |
* {@code | |
* Tokenizer tokenizer = new Tokenizer("Olá!! Isto «é» um teste...", TokenizationMode.DELETE_PONTUATION); | |
* while (tokenizer.hasNext()) { | |
* System.out.println(tokenizer.next()); | |
* } | |
* | |
* // Irá imprimir: | |
* // Olá | |
* // Isto | |
* // é | |
* // um | |
* // teste | |
* } | |
* <pre> | |
* | |
* @author PSantos | |
* @version 4 de Abril de 2014 | |
*/ | |
public class Tokenizer implements Iterable<String>, Iterator<String> { | |
/** | |
* Modo como tokenizador trata os tokens iniciados ou terminados | |
* por algo que não seja uma letra ou número. | |
*/ | |
public enum TokenizationMode { | |
/** | |
* Considera um token tudo que está entre espaços. | |
* Exemplo: 'Esta, é u.m.a. frase «de» exemplo...' | |
* Resulta em: 'Esta,' 'é' 'u.m.a.' 'frase' '«de»' 'exemplo...' | |
*/ | |
SIMPLE, | |
/** | |
* Primeiro considera um token tudo que está entre espaços, eliminando | |
* depois do inicio e fim do token todos os caracteres que não | |
* sejam [a-zA-Z_0-9]. | |
* | |
* Exemplo: 'Esta, é u.m.a. frase «de» exemplo...' | |
* Resulta em: 'Esta' 'é' 'u.m.a' 'frase' 'de' 'exemplo' 'null' | |
* @TODO: Tratar o caso em que devolve null, porque a frase não termina em letra num número | |
*/ | |
DELETE_PONTUATION, | |
/** | |
* Funciona como o modo DELETE_PONTUATION, mas em vez de eliminar os | |
* caracteres dos extremos das palavras, limita-se a separa-los e a | |
* trata-los como sendo outro token. | |
* Exemplo: 'Esta, é u.m.a. frase «de» exemplo...' | |
* Resulta em: 'Esta' ',' 'é' 'u.m.a' '.' 'frase' '«' 'de' '»' 'exemplo' '...' | |
*/ | |
SEPARATE_PONCTUATION | |
} | |
/** Texto a dividir em tokens */ | |
private String _text; | |
/** Modo de funcionamento do tokenizador por omissão */ | |
private TokenizationMode DEFAULT_MODE = TokenizationMode.SEPARATE_PONCTUATION; | |
/** Modo de funcionamento do tokenizador */ | |
private TokenizationMode mode; | |
/** | |
* Permite obter um tokenizador especificamente para o texto indicado pelo | |
* parâmetro {@code text}. A tokenização é feita com base em espaços no | |
* modo {@link TokenizationMode#SEPARATE_PONCTUATION}. | |
* | |
* @param text texto a tokenizar. | |
*/ | |
public Tokenizer(String text) { | |
initialise(text, DEFAULT_MODE); | |
} | |
/** | |
* Permite obter um tokenizador especificamente para o texto indicado pelo | |
* parâmetro {@code text}. A tokenização é feita com base em espaços no | |
* modo indicado pelo parâmetro {@code mode}. | |
* | |
* @param text texto a tokenizar. | |
* @param mode modo como tokenizador trata os tokens iniciados ou terminados | |
* por algo que não seja uma letra ou número. | |
*/ | |
public Tokenizer(String text, TokenizationMode mode) { | |
initialise(text, mode); | |
} | |
/** | |
* Inicializa o Tokenizador. | |
* @param text texto a tokenizar. | |
* @param mode modo como tokenizador trata os tokens iniciados ou terminados | |
* por algo que não seja uma letra ou número. | |
*/ | |
private void initialise(String text, TokenizationMode mode) { | |
this.mode = mode; | |
setSentence(text); | |
} | |
@Override | |
public Iterator<String> iterator() { | |
return this; | |
} | |
/** | |
* Indica se a frase dividida em tokens, possui mais tokens. | |
* @return {@code true} se existe pelo menos mais um token, {@code false} | |
* caso contrário. | |
*/ | |
@Override | |
public boolean hasNext() { | |
return (_text != null); | |
} | |
/** | |
* Devolve o próximo token no texto dividido em tokens. | |
* @return o próximo token. | |
*/ | |
@Override | |
public String next() { | |
return nextWord(_text); | |
} | |
/** | |
* Não faz nada. Precisa existir pois esta classe implementa o interface | |
* {@link java.util.Iterator}. | |
*/ | |
@Override | |
public void remove() { | |
} | |
protected void setSentence(String _sentence) { | |
// Se está a definir a sentence pela primeira vez, processa-a. | |
if (this._text == null && | |
(mode == TokenizationMode.SEPARATE_PONCTUATION || | |
mode == TokenizationMode.DELETE_PONTUATION) ) { | |
this._text = separatePonctuation(_sentence); | |
} else { | |
this._text = _sentence; | |
} | |
} | |
protected String nextWord(String _sentence) { | |
switch (this.mode) { | |
case SIMPLE: | |
return nextWordSpaceAsDelimiter(_sentence); | |
case DELETE_PONTUATION: | |
return nextWord0(_sentence); | |
case SEPARATE_PONCTUATION: | |
return nextWordSpaceAsDelimiter(_sentence); | |
} | |
return null; | |
} | |
/** | |
* | |
* This routine takes a string ("_text") and returns the first word | |
* found. It also modifies the sentence parameter, trimming off the found | |
* word. | |
* | |
* As triggers can't (apparently) create temporary tables, the | |
* "parseWords" method of splitting the words into a temporary table in | |
* one go and using a cursor to iterate through them cannot be used in the | |
* indexing task. So instead, this self-contained function can be used | |
* in this specific case. | |
* | |
* Example 1: | |
* Input : ',Frase O.N.U O.N. 12-04-2011 «citação de»' | |
* Output : | |
* Frase | |
* O.N.U | |
* O.N. | |
* 12-04-2011 | |
* citação | |
* de» | |
* | |
* Exemple 2: | |
* Input : ', Frase O.N.U O.N . 12-04-2011 « citação de »' | |
* Output : | |
* Frase | |
* O.N.U | |
* O.N | |
* 12-04-2011 | |
* citação | |
* de | |
* | |
* @param _sentence | |
* @return | |
*/ | |
protected String nextWord0(String _sentence) { | |
// -- If the sentence is null or blank, then skip. | |
if (_sentence == null || "".equals(_sentence)) { | |
this._text = null; | |
return null; | |
} | |
// -- Character-counting gubbins. | |
char _nextChar; | |
int _len = 0; // -- Length of sentence | |
int _base = 0; // -- Start of word | |
int _incr = 0; // -- End of word (absolute position) | |
// -- Get length of sentence | |
_len = _sentence.length(); | |
_nextChar = _sentence.charAt(_base); // my | |
// -- Slew through leading non-word chars | |
while (!Character.isLetterOrDigit(_nextChar) && _base < _len) { | |
_base = _base + 1; | |
if (_base < _len) { | |
_nextChar = _sentence.charAt(_base); | |
} | |
} | |
// -- If no char was found then this string doesn't contain any word chars. | |
// @TODO verificar se esta linha não impede de apanhar a ùltima palavra da_sentence | |
if (_base >= _len) { | |
setSentence(null); | |
return null; | |
} | |
// -- Start letter counting from the base position. | |
_incr = _base; | |
// -- Clear word. | |
String _word = ""; | |
// -- Slew through word looking for first non-word character, building the | |
// -- result word as we go. | |
while (!Character.isWhitespace(_nextChar) && _incr < _len) { | |
_word = _word + _nextChar; | |
_incr = _incr + 1; | |
if (_incr < _len) // My if | |
{ | |
_nextChar = _sentence.charAt(_incr); | |
} | |
} | |
// -- If we found the end of the string, then force _text to NULL. Else, | |
// -- chop off the found word from the sentence for next time. | |
if (_incr >= _len) { | |
setSentence(null); | |
} else { | |
setSentence(_sentence.substring(_incr)); | |
} | |
return _word; | |
} | |
/** | |
* Para esta função uma palavra (ou mais correctamente, um token) é algo que | |
* se encontra entre espaços (ou entre inicio da frase e espaço, ou ainda | |
* entre espaço e fim da frase). | |
* | |
* Example 1: | |
* Input : ',Frase O.N.U O.N. 12-04-2011 «citação de»' | |
* Output: | |
* ,Frase | |
* O.N.U | |
* O.N. | |
* 12-04-2011 | |
* «citação | |
* de» | |
* | |
* | |
* Example 2: | |
* Input : ', Frase O.N.U O.N . 12-04-2011 « citação de »' | |
* output: | |
* , | |
* Frase | |
* O.N.U | |
* O.N | |
* . | |
* 12-04-2011 | |
* « | |
* citação | |
* de | |
* » | |
* | |
* @param _sentence | |
* @return | |
*/ | |
protected String nextWordSpaceAsDelimiter(String _sentence) { | |
String[] parts = _sentence.split("\\s+", 2); | |
if (parts == null) { | |
return null; | |
} | |
if (parts.length == 2) { | |
setSentence(parts[1]); | |
return parts[0]; | |
} else { | |
setSentence(null); | |
return parts[0]; | |
} | |
} | |
/** | |
* Tenta separar tudo que não seja letra nem número, do inicio e fim | |
* das palavras. | |
* | |
* @param sentence | |
* @return | |
*/ | |
protected String separatePonctuation(String sentence) { | |
if (sentence == null) | |
return null; | |
sentence = sentence.trim(); | |
if(sentence.equals("")) | |
return null; | |
// Como '\\p{Punct}' não inclui todos os caracteres não letras e números, é preciso também: | |
// \\p{Pi} e \\p{Pf} para incluir qualquer tipo de opening quote and closing quote respectivamente (ex.“, ”, «, »). | |
String patternIni = "^([^\\p{L}0-9]+)(.*)"; // "^([\\p{Punct}\\p{Pi}\\p{Pf}]+)(.*) || "^(\\W+)(.*)"; | |
// Notar que: ([\\p{Punct}»«]+) é diferente de ([^\\w]+). Se a primeira expressão | |
// for substituida por esta última, palavras terminas em caracteres acentuados | |
// são divididas. Exemplo: "já" -> j á. | |
String patternEnd = "(.*?)([^\\p{L}0-9]+)$"; // "(.*?)([\\p{Punct}\\p{Pi}\\p{Pf}]+)$" | |
String finalSentence = null; | |
String[] parts = sentence.split("\\s+"); | |
for (int i = 0; i < parts.length; i++) { | |
String potencialIni = parts[i].replaceFirst(patternIni, "$1"); | |
// Se existe caracter(s) de pontuação no incio da palavra | |
if (potencialIni.length() < parts[i].length()) { | |
if (finalSentence == null) { | |
finalSentence = potencialIni; | |
} else { | |
finalSentence = finalSentence + " " + potencialIni; | |
} | |
parts[i] = parts[i].replaceFirst("^" + Pattern.quote(potencialIni), ""); | |
} | |
String replacement = "$1 $2"; | |
if(this.mode.equals(TokenizationMode.DELETE_PONTUATION)) { | |
replacement = "$1"; // Elimina os "sinais de pontuação" do fim do token | |
} | |
if (finalSentence == null) { | |
finalSentence = parts[i].replaceFirst(patternEnd, replacement); | |
} else { | |
// Acrescenta o token separado dos catacteres de pontuação finais, se existirem. (e.g. exemplo... -> exemplo ...) | |
finalSentence = finalSentence + " " + parts[i].replaceFirst(patternEnd, replacement); | |
} | |
} | |
return finalSentence; | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment