Dividindo String com split

25 respostas
E

Olá pessoal!
Estou com uma duvida aqui e até agora não consegui achar um solução pra ela.
É o seguinte tenho uma String assim:

String registro = ("1, \"Joao da Silva\", \"Rua Dois\",12, \"Villa\" ");

e quando faço:

arrayCampos = registro.split(",");

ele divide os campos certinho, até aqui tudo bem.
O problema é que se em algum campo da String “registro” tivesse um nome com uma virgula no meio exemplo:

String registro = ("1, \"Joao, da Silva\", \"Rua Dois\",12, \"Villa\" ");

o split dividiria o nome em duas partes, entenderam?

Gostaria de saber se tem alguma maneira de dividir os campos dessa String por vírgula “,” , mais que ignorasse
tudo que estivesse entre aspas duplas.

Se alguém puder ajudar agradeço a atenção, valeu!

25 Respostas

B

Neste caso, você precisa usar um StreamTokenizer (cuidado, é StreamTokenizer, não StringTokenizer). Isso é por causa do tratamento das aspas e do que está dentro das aspas.

jaboot

Use o commons lang3 do apache. O StringUtils deles tem n funções. Você resolve isso rapidinho e null safe.

B

Rapidinho, null-safe, mas não resolve o problema dele :frowning: - eu li as descrições dos métodos e nenhum deles resolve esse problema em particular :slight_smile:

DavidUser

Ou utilize seu próprio algoritmo, ou pode fazer o split normalmente para virgula e então com uma segunda passada por cada campo se o atual possui aspas duplas concatena com o próximo até que o ultimo campo concatenado também contenha aspas duplas.

Trocando em miúdos junte os campos com aspas duplas, ora.

E

Esse é o problema típico de quem vai ler um arquivo CSV, quando o CSV tem strings cercadas com aspas. Usar um StreamTokenizer, nesse caso, é bem simples.

DavidUser

Você ainda pode fazer o split para aspas duplas e então fazer um segundo split para todos os campos que começa ou terminam com virgulas

Editado:“Só serve se as strings entre aspas duplas não contem vírgulas no começo e fim”

lucasportela

Se tiver um padrão, use uma expressão regular (http://pt.wikipedia.org/wiki/Express%C3%A3o_regular) para quebrar por números ou aspas seguidos de vígurla.

algo nesse sentido:"\"," || "[0-9],"

que fique bem claro que esse código não está correto, vai ter que estudar expressão regular ou bolar um algoritmo. Tente achar padrões nas Strings que você tem.

Rodrigo_Sasaki

lucasportela:
Se tiver um padrão, use uma expressão regular (http://pt.wikipedia.org/wiki/Express%C3%A3o_regular) para quebrar por números ou aspas seguidos de vígurla.

algo nesse sentido:"\"," || "[0-9],"

que fique bem claro que esse código não está correto, vai ter que estudar expressão regular ou bolar um algoritmo. Tente achar padrões nas Strings que você tem.


Não vejo como fazer com regex.

Não tem como saber se a vírgula está dentro do nome, ou se está entre 2 dados, pois nas duas situações ela estaria entre 2 aspas duplas.

Se tem eu não sei como

lucasportela

Rodrigo Sasaki:
lucasportela:
Se tiver um padrão, use uma expressão regular (http://pt.wikipedia.org/wiki/Express%C3%A3o_regular) para quebrar por números ou aspas seguidos de vígurla.

algo nesse sentido:"\"," || "[0-9],"

que fique bem claro que esse código não está correto, vai ter que estudar expressão regular ou bolar um algoritmo. Tente achar padrões nas Strings que você tem.


Não vejo como fazer com regex.

Não tem como saber se a vírgula está dentro do nome, ou se está entre 2 dados, pois nas duas situações ela estaria entre 2 aspas duplas.

Se tem eu não sei como

O padrão que eu achei ali foi uma (aspa ou número) seguido de vírgula, daria certo… caso todas as strings sigam este padrão

Rodrigo_Sasaki

Opa, edit sacana hehehe, agora sim entendi o que você quis dizer :slight_smile:

lucasportela

Opa, edit sacana hehehe, agora sim entendi o que você quis dizer :)

Eu li e vi que causou ambiguidade e percebi que só tem programador ao pé da letra aqui e botei na sintaxe de programador

R

Porque colocariam virgula num nome proprio ?

Rodrigo_Sasaki

Opa, edit sacana hehehe, agora sim entendi o que você quis dizer :)

Eu li e vi que causou ambiguidade e percebi que só tem programador ao pé da letra aqui e botei na sintaxe de programador

Hehehehehe, pra ser mais chato ainda, o problema não é o programador :slight_smile: é o programa ^^
Mas pra ser justo seu código já dizia o que você queria dizer.

Ficou legal, só tem que tomar cuidado pois ao fazer o split assim, ele vai levar o número junto.

lucasportela

Ali foi um exemplo, mas é comum ter em endereços

“Rua 25, Conjunto K, casa 09”

R

Entendi ‘-’

Rodrigo_Sasaki

Ou até citações bibliográficas. que fica algo assim: SASAKI, R. P.

R
String registro = ("1, \"Joao, da Silva\", \"Rua Dois\",12, \"Villa\" ");

Seria mais pratico voce dividir essa String em variaveis diferentes, nome para nome, endereco para endereco e etc… Nao sei como é esse seu sistema, mas provavelmente voce tera problemas de manutencoes, como agora.

E

http://docs.oracle.com/javase/6/docs/api/java/io/StreamTokenizer.html

Essa é uma das classes mais antigas do JDK. Acho que o próprio Gosling, ou alguém que escreveu o javac, criou essa classe para dar ao programador um subconjunto bem restrito das rotinas básicas que alguém usaria para criar um compilador ou interpretador.

E

o amigo entanglement, poderia me citar um exemplo? Eu dei uma lida na API e não entendi muito bem…

DavidUser

Com essa função você pode passar um a lista de delimitadores da menor para maior prioridade e a string:

static public Vector<String> splitMultipleDelimiters(String str, char delimiters[]) {
        Vector<String> finalTokens = new Vector<String>();
        
        final char NATURAL_DELIMITER = delimiters[0];
        char delimiter = NATURAL_DELIMITER;
        String field = "";
        boolean unique = false;
        for (int i = 0; i < str.length(); i++) {
            int j;
            for (j = 1; j < delimiters.length; j++)
                if (str.charAt(i) == delimiters[j] && delimiter == NATURAL_DELIMITER) {
                    delimiter = delimiters[j];
                    break;
                }
            if (j < delimiters.length) continue;
            
            if (str.charAt(i) == delimiter) {
                if (!field.isEmpty())
                    finalTokens.add(field);
                field = "";
                delimiter = NATURAL_DELIMITER;
            }
            else
                field += str.charAt(i);
        }
        
        return finalTokens;
    }

Exemplo de uso:

char delimiters[] = {',', '"'};
        Vector<String> list = splitMultipleDelimiters("1,*\"Joao, Silva\", 2, 3*,\"Maria\",45", delimiters);
Marcos_Henrique_N_Al

Olá Evandro,

Se não for obrigado a usar o separador virgula "," procure usar um caractér que seja pouco usado, por exemplo o "^" para ser o separador.

Nesse caso o método split não funciona da forma que funciona com o caracter ",".
Mas é bem simples fazer o split entender que você quer usar o "^" como separador.
Tem que usar o Pattern.quote() para o split conhecer o "^".

Segue modelo do código:
import java.util.regex.Pattern;

public class TesteCase {

	public static void main(String[] args) {

		String[] colunas = { "Nome da Pessoa", "Endereço", "Bairro", "Telefone" };

		String registroDeAgenda = "Marivalda de Oliveira^Rua Jaraguá, número 10, casa 300^Centro^9999-9999";

		String campos[] = registroDeAgenda.split(Pattern.quote("^")); // É só usar o Pattern.quote.

		for (int i = 0; i < colunas.length; i++) {
			System.out.println(colunas[i] + ": " + campos[i]);
		}
	}
}

Agora se for obrigado a usar a vírgula... vai ficar complicado.

E

Note que um CSV “de verdade” (tal como o que você passou) é mais fácil de ler que um CSV “padrão brasileiro” (separado por ";’ e com números com vírgulas).

package guj;

import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

public class ExemploStreamTokenizer {

    /**
     * Este exemplo mostra como ler uma linha de um arquivo CSV (padrão americano, ou seja, separado por vírgulas, e os
     * números são com pontos
     */
    public void exemplo1() throws IOException {
        String csv = "1, \"Rua dos Bobos, 0\", 1.234, \"Kafka escreveu \\\"A Metamorfose\\\", livro publicado em 1915, em 1912.\"";
        StreamTokenizer st = new StreamTokenizer(new StringReader(csv));
        st.parseNumbers(); // StreamTokenizer supõe que os números são em formato americano
        List<Object> campos = new ArrayList<Object>();
        for (int tokenType = st.nextToken(); tokenType != StreamTokenizer.TT_EOF; tokenType = st.nextToken()) {
            switch (tokenType) {
            case StreamTokenizer.TT_EOL:
                continue;
            case StreamTokenizer.TT_NUMBER: // números com ponto decimal e talvez em notação científica
                campos.add(st.nval);
                break;
            case StreamTokenizer.TT_WORD: // palavras soltas
                campos.add(st.sval);
                break;
            case '\"': // campos entre aspas
                campos.add(st.sval);
                break;
            case ',': // pulando os delimitadores
                break;
            default:
                campos.add(tokenType);
                break;
            }
        }
        System.out.println("Exemplo1: ");
        System.out.println("Entrada: " + csv);
        System.out.println("Analisado:");
        for (int i = 0; i < campos.size(); ++i) {
            System.out.printf("%d: %s\n", i + 1, campos.get(i));
        }
    }

    /**
     * Este exemplo mostra como ler uma linha de um arquivo CSV (padrão brasileiro, ou seja, separado por ";", e os
     * números são com vírgulas
     */
    public void exemplo2() throws IOException {
        String csv = "1; \"Rua dos Bobos, 0\"; 1,234; \"Este texto é disponibilizado nos termos da licença Creative Commons; pode estar sujeito a condições adicionais.\"";
        StreamTokenizer st = new StreamTokenizer(new StringReader(csv));
        st.resetSyntax();
        st.wordChars('A', 'Z');
        st.wordChars('a', 'z');
        st.whitespaceChars(0, ' ');
        st.quoteChar('\"');
        st.slashStarComments(true);
        st.slashSlashComments(true);

        // Note que StreamTokenizer supõe que os números têm pontos, não vírgulas.
        // Ou seja, temos de nós mesmos analisar os números :(
        List<Object> campos = new ArrayList<Object>();
        StringBuilder currentNumber = new StringBuilder();
        boolean prevTokenWasPartOfNumber = false;
        for (int tokenType = st.nextToken(); tokenType != StreamTokenizer.TT_EOF; tokenType = st.nextToken()) {
            switch (tokenType) {
            case StreamTokenizer.TT_EOL:
                if (prevTokenWasPartOfNumber && currentNumber.length() > 0) {
                    campos.add(currentNumber.toString());
                }
                break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case ',':
                currentNumber.append((char) tokenType);
                prevTokenWasPartOfNumber = true;
                break;
            case StreamTokenizer.TT_WORD: // palavras soltas
                if (prevTokenWasPartOfNumber && currentNumber.length() > 0) {
                    campos.add(currentNumber.toString());
                    currentNumber.setLength(0);
                    prevTokenWasPartOfNumber = false;
                }
                campos.add(st.sval);
                break;
            case '\"': // campos entre aspas
                if (prevTokenWasPartOfNumber && currentNumber.length() > 0) {
                    campos.add(currentNumber.toString());
                    currentNumber.setLength(0);
                    prevTokenWasPartOfNumber = false;
                }
                campos.add(st.sval);
                break;
            case ';': // pulando os pontos-e-vírgulas
                if (prevTokenWasPartOfNumber && currentNumber.length() > 0) {
                    campos.add(currentNumber.toString());
                    currentNumber.setLength(0);
                    prevTokenWasPartOfNumber = false;
                }
                break;
            default:
                if (prevTokenWasPartOfNumber && currentNumber.length() > 0) {
                    campos.add(currentNumber.toString());
                    currentNumber.setLength(0);
                    prevTokenWasPartOfNumber = false;
                }
                campos.add(tokenType);
                break;
            }
        }
        System.out.println();
        System.out.println("Exemplo2: ");
        System.out.println("Entrada: " + csv);
        System.out.println("Analisado:");
        for (int i = 0; i < campos.size(); ++i) {
            System.out.printf("%d: %s\n", i + 1, campos.get(i));
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        ExemploStreamTokenizer est = new ExemploStreamTokenizer();
        est.exemplo1();
        est.exemplo2();
    }

}
E

Bom dia entanglement !

Eu estou finalizando um metodo que criei para suprir minha necessidade.
Ontem dei uma olhada no StreamTokenizer que vc me falou, mais não compreendi muito bem.
Você poderia me dar um exemplo de como eu poderia usar o StreamTokenizer?

Desde já agradeço a atenção.

E

Desconsiderem minha ultima resposta!!!

Falha minha … :oops:

E

Muito obrigado a todos, tanto pela atenção quanto pela rapidez!!!

Vou estudar todas possibilidades que vocês me apresentaram e aplicar no meu
projeto pra ver como fica.

Valeu galera!!!

Criado 6 de fevereiro de 2013
Ultima resposta 7 de fev. de 2013
Respostas 25
Participantes 9