Skip to content

Instantly share code, notes, and snippets.

@i000313
Created April 4, 2014 22:10
Show Gist options
  • Save i000313/9984096 to your computer and use it in GitHub Desktop.
Save i000313/9984096 to your computer and use it in GitHub Desktop.
This is a tokenizer based on spaces. #java #spacetokenizer #tokenizer
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