Match de espaços no meio, com regex

Olá pessoal,

Não sou muito bom com regex complexas.
Estou tentando escrever uma regex que encontre apenas os espaços internos de uma String, para que eu possa substituir por um único espaço.

Por exemplo:

// Ao se fazer um replace em:
"Teste        de        regex"
// deveria retornar:
"Teste de regex"
// Mas se a string for:
"      Teste         de     regex                    "
//deveria retornar:
"      Teste de regex                    " // sem fazer o trim das pontas

Fazer o replace de todos os espaços sequenciais por um único é tranquilo.

"  Teste    de      regex    ".replaceAll("\\s+", " ");

Meu problema é que eu preciso que espaços das pontas sejam ignorados.

Se alguém bom de regex puder me dar uma dica, eu ficaria muito grato :slight_smile:

"  Teste    de      regex    ".replaceAll("\\s+", " ").replaceAll ("^\\s+", "").replaceAll ("\\s+$", "") 

olá entanglement,

obrigado pela força, mas eu acredito que vc não entendeu o problema.
a sua solução retira os espaços do início/ fim da string tbém (faz o trim).

Eu estou tentando remover os espaços entre palavras da frase sem que os espaços no início e no fim sejam alterados.

Só para exemplificar,

hoje eu faço isso assim:

public class MainClass {
    public static void main(String[] args) {
        MainClass teste = new MainClass();
        String result = teste.middleTrim("      Teste    de      middle      trim      ");
        System.out.println(result);
    }
    public String middleTrim(final CharSequence text) {
        Pattern LR_SPACE_REGEX = Pattern.compile("^\\s+|\\s+$");
        Pattern ANY_SPACE_REGEX = Pattern.compile("\\s+");

        // busca espaços nas pontas
        Matcher lrMatcher = LR_SPACE_REGEX.matcher(text);
        List<Region> lrRegions = new ArrayList<Region>();
        while (lrMatcher.find()) {
            // mantém as regioes encontradas em ordem decrescente.
            lrRegions.add(0, new Region(lrMatcher.start(), lrMatcher.end()));
        }

        // busca espaços em qquer lugar
        Matcher anyMatcher = ANY_SPACE_REGEX.matcher(text);
        List<Region> anyRegions = new ArrayList<Region>();
        while (anyMatcher.find()) {
            // mantém as regioes encontradas em ordem decrescente.
            anyRegions.add(0, new Region(anyMatcher.start(), anyMatcher.end()));
        }

        // exclui os resultados das pontas do resultado "anywhere"
        anyRegions.removeAll(lrRegions);

        StringBuilder builder = new StringBuilder(text);
        // faz a substituicao
        for (Region region : anyRegions) {
            builder.replace(region.getStart(), region.getEnd(), " ");
        }
        return builder.toString();
    }
    static class Region {
        private int start;
        private int end;
        public Region(int start, int end) {
            this.start = start;
            this.end = end;
        }
        public int getStart() {
            return start;
        }
        public int getEnd() {
            return end;
        }
        @Override
        public int hashCode() {
            int hash = 5;
            hash = 53 * hash + this.start;
            hash = 53 * hash + this.end;
            return hash;
        }
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Region other = (Region) obj;
            if (this.start != other.start) {
                return false;
            }
            if (this.end != other.end) {
                return false;
            }
            return true;
        }
    }
}

que basicamente é uma gambiarra com um pouco de teoria dos conjuntos:

  1. busco todas as ocorrencias de espaços sequenciais (vou chamar de ANY)
  2. busco as ocorrencias de espaços sequenciais no início e/ou fim (vou chamar de LR)
  3. faço a subtração >> ANY - LR = MIDDLE
  4. então faço o replace (de tras pra frente para nao invalidar as regioes encontradas).

seria interesante se houvesse um jeito de através de uma regex efetuar os passos 1, 2 e 3 de uma forma mais elegante.

:slight_smile:

Ah, você NÂO quer tirar os espaços das extremidades.
Uma forma trivial de fazer isso (já que escrever expressões regulares que NÂO BATAM com alguma coisa é muito mais difícil que escrever expressões regulares que BATAM com alguma coisa) é guardar os espaços das extremidades, efetuar a troca, e depois repor os espaços.

Eu não recomendaria usar uma expressão regular que NÃO BATA com alguma coisa (que é o caso de usar “(?!X)” ou “(?<!X)” ) porque essas expressões são difíceis de escrever. De qualquer maneira, você pode tentar ver em:

http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html

olá entanglement,

isso mesmo! eu queria apenas normalizar os espaços entre palavras.
esse esquema que vc citou de recolocar os espaços é mais ou menos a idéia que implementei no algoritmo anterior.
só que ao invés de guardar o prefixo e/ou sufixo de espaços, eu guardo as coordenadas e as ignoro na substituição.
funciona. eu só estava tentando melhorá-lo utilizando apenas regex se possível. :smiley:

estudando um pouco sobre regex em http://www.regular-expressions.info eu cheguei a essa expressão:

System.out.println(&quot;  teste    de     regex     &quot;.replaceAll(&quot;(?&lt;=\\S)(\\s+)(?=\\S)&quot;, &quot; &quot;));

onde fiz um lookbehind e um lookahead na minha expressão "\s+" procurando por "não espaços", que aparentemente faz o que eu quero.

agora estou analisando se não há alguma situação que esta regex não funcione.