Mostrando postagens com marcador String. Mostrar todas as postagens
Mostrando postagens com marcador String. Mostrar todas as postagens

quarta-feira, 9 de novembro de 2016

Executando um case insensitive match em strings

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Algumas vezes já estive em situações em que precisava aplicar uma expressão regular em textos que desconhecia como era o emprego de letras maiúsculas e minúsculas, ou seja, não era possível determinar a forma do texto. Então, cheguei a um impasse: o que fazer ?

Eis aqui algumas opções que pensei:
  1. Transformar todas as letras do texto em minúsculas.
  2. Transformar todas as letras do texto em maiúsculas.
  3. Modificar minha expressão regular para ser case-insensitive.
Mas o que é case-insensitive ? É tudo aquilo que não diferencia maiúscula de minúscula, e vice-versa. Então, neste tipo de análise "PRECISO ESTUDAR SEMPRE" é igual a "preciso estudar sempre", que é igual a "Preciso Estudar Sempre" e assim por diante. Já o seu caso contrário, o case-sensitive, diferencia. Então, no nosso exemplo todas as palavras seriam consideradas diferentes umas das outras.

Case-sensitive = Diferencia maiúsculo de minúsculo
Case-insensitive = Não diferencia maiúsculo de minúsculo

Transformar todas as letras do texto em maiúsculas ou minúsculas me obrigaria a mapear todos os pontos que a leitura do texto era feita. Se houvessem muitos pontos, isso exigiria muitas modificações e muitas modificações exigem muitos testes. Então no final, o que era para ser algo fácil se tornou algo, talvez, um pouco complicado.

A única opção que resta é modificar a expressão regular, mas como fazer isso ? Mapear todos os intervalos de letras com maiúsculas e minúsculas, por exemplo [a-zA-Z], não é a melhor solução, pois se a expressão for muito grande com muitos intervalos podemos ter o mesmo problema que teríamos adotando a primeira ou segunda opção. Muitas modificações e talvez alguns erros.

Através de pesquisa consegui chegar a melhor solução. Em Java, assim como em outras linguagens, a respectiva API de expressões regulares oferece uma flag para case-insensitive. Como já citei em outros posts que possuo mais intimidade com a linguagem Java, mostrarei a aplicação dessa solução nesta plataforma, mas nada impede que você ache a mesma solução na linguagem de sua preferência.

1:  import java.util.regex.Pattern;  
2:    
3:  class CaseInsensitivePattern {  
4:       public static void main(String[] args) {  
5:            //Primeira forma de aplicação através da constante provida pela própria API.  
6:            System.out.println("Case insensitive match via constant: " + Pattern.compile("^([a-z]*\\s*)*$",Pattern.CASE_INSENSITIVE).matcher("PrEcIsO eStUdAr SeMpRe").matches());  
7:            System.out.println("Case sensitive match via constant: " + Pattern.compile("^([a-z]*\\s*)*$").matcher("PrEcIsO eStUdAr SeMpRe").matches());  
8:    
9:            //Segunda forma de aplicação através de uma flag na própria regex.  
10:            System.out.println("Case insensitive match via String: " + "PrEcIsO eStUdAr SeMpRe".matches("(?i)^([a-z]*\\s*)*$"));  
11:            System.out.println("Case sensitive match via String: " + "PrEcIsO eStUdAr SeMpRe".matches("^([a-z]*\\s*)*$"));  
12:       }  
13:  }  

Note que forneci através do exemplo duas formas de executar uma expressão regular em case-insensitive. Nas linhas 6 e 7, apresento abordagens de uso da constante Pattern.CASE_INSENSITIVE, da classe Pattern, a qual simboliza que a dada expressão deve ser executada na forma configurada. Respectivamente, uma executa um case insensitive match e a outra um case sensitive match.

Nas linhas 10 e 11, utilizo uma outra abordagem. É possível embutir na própria expressão regular uma flag sinalizando que esta deve realizar uma case insensitive match. Tal é atingido através da flag (?i).

Entre essas duas abordagens apresentadas. Qual utilizar ?

Como sempre digo aqui, não existem balas de prata. Quem vai determinar a melhor solução é a sua necessidade. Talvez, para alguma situação é melhor transformar o texto para maiúsculo ou minúsculo do que modificar a regex, ou para outras situações é mais vantajoso trabalhar em cima da expressão. Contudo, para modificações na expressão, entre as duas opções apresentadas, eu ficaria com a segunda pelo motivo dessa ser mais desacoplada. Seria possível assim configurar minha expressão em um arquivo externo (txt, properties, etc.) e não precisar modificar meu programa principal. Caso algum erro aconteça, eu sei que o único ponto possível seria neste arquivo.

É possível configurar mais de uma flag nas duas abordagens, por exemplo, se é desejado executar case insensitive match em uma string que possua caracteres acentuados, ou um multiline. Por padrão a flag (?i) não cobre casos de caracteres Unicode. Para tal é necessário utilizar em conjunto a flag (?u), resultando em (?iu).

Deixarei neste post referências para outros dois posts, onde falo sobre a opção multiline e caracteres Unicode, e uma ferramenta muito boa para validação de expressões regulares.

Para baixar o código:
Link do repositório GitHub: https://github.com/PrecisoEstudarSempre/CaseInsensitivePattern.git
Link do GoogleDrive: https://drive.google.com/file/d/0BzDmhBY6luU6V2ZoSjFpUmRCYlk/view?usp=sharing
Link do Dropbox: https://www.dropbox.com/s/uv4vgwxnamteujt/CaseInsensitivePattern.rar?dl=0

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA
Leia Mais ››

domingo, 16 de outubro de 2016

O que você sabia sobre tamanhos de strings talvez não está tão certo assim.

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Inicio este post com uma pergunta. Para a string abaixo, qual seria seu tamanho ?

 joão  

Embora pareça simples, não a subestime, pois se você pensou que a resposta seria 4, talvez esteja errado. Sim, você não leu errado, esta string de fato pode não possuir tamanho 4 embora possua quatro caracteres.

Porque isso acontece ?

Antes de responder a essa pergunta, explicarei qual foi a minha motivação para escrever esse post. Uma vez trabalhei em um sistema web feito em Java que possuía um formulário HTML e em um dos campos a validação de tamanho estava acusando que estávamos enviando uma string maior que permitido. Estudando o problema, descobrimos que isso acontecia porque uma das letras possuía um acento. Agora que já contextualizamos toda situação, devemos voltar para a busca de uma resposta para a nossa pergunta.

Quando desejamos descobrir o tamanho de uma string em Java utilizamos o método length(). Se dermos uma olhada na documentação dele veremos o seguinte:
Returns the length of this string. The length is equal to the number of Unicode code units in the string.
OBSERVAÇÃO: A linguagem Java está sendo usado neste post como uma forma dar corpo ao estudo do nosso problema e consequentemente da solução dele. Tal situação também pode estar ser encontrada em outras linguagens.

Então, o método length() não trabalha da forma que pensamos. Ele não conta caracteres, e sim Unicode code units. Como o próprio nome já diz, eles são unidades de códigos Unicode e quem dita o tamanho de cada unidade dessa é o tipo de encoding usado.

Um encoding é uma forma de representação de uma caractere. Cada tipo possui definições diferentes, onde nestas constam os caracteres que são aceitos e suas respectivas posições na tabela ASCII. No nosso problema o que acontecia era que o encoding type utilizado no envio do texto do browser para o servidor não aceitava caracteres com acentos.

Um dos encoding type mais conhecidos e utilizados é o UTF-8, onde ele define que um Unicode code point que vai de 0 à 127 na tabela ASCII é armazenado em uma code unit de 8 bits, e os que estão acima disso podem ser armazenados em 2, 3 ou até 6 bytes. Sua fama se deve ao fato de ele aceitar caracteres com acentos.

Note que agora introduzimos um novo elemento no nosso estudo, o code point. Na terminologia de encoding um code point é qualquer valor numérico que compõe o espaço de um código. O esquema de encoding de caracteres ASCII compreende 128 code points, o Extended ASCII define 256 code points e o Unicode compreende 1114112 code points. Neste último, um code point é representado visualmente pelo símbolo U+ seguido de uma representação hexadecimal do caractere.

A solução para problemas como este é sempre definir o encoding type do seu IO, seja ela uma página HTML, um arquivo, ou algum outro tipo de stream. Um exemplo claro da falta de definição de encoding type é quando você recebe aquele spam chinês ou indiano e quando vai abrir aparecem várias caixinhas ou pontos de interrogação. Um teste que pode ser feito em casa é criar uma página HTML qualquer, não definir o enconding da página e por um texto com acentos. Quando você for abrir essa página em seu browser notará que no lugar dos caracteres com acentos, aparecerão caracteres estranhos. Isto acontece devido ao fato do browser não saber como ele deve processar aquele texto, então ele processa da forma que ele bem achar correto.

Se você quiser entender mais da história do Unicode, recomendo fortemente a leitura do post "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)". Vale a pena a leitura.

Talvez você tenha notado que nos primeiros parágrafos eu disse que a string em questão podia não ter tamanho 4. Em algumas ocasiões ela pode apresentar tamanho 5. Isso se tornou uma grande curiosidade que me estimulou para obter mais respostas para a construção deste post. Faça o seguinte teste:

Crie uma classe Java com o mesmo conteúdo abaixo, compile e rode este programa pelo cmd do windows.

 class StringLength {  
      public static void main(String[] args) {            
           String s = "joão";  
           System.out.println("Nossa string:" + s);  
           System.out.println("O tamanho(length) da nossa string: " + s.length());  
      }       
 }  

Para compilar e rodar o programa pelo cmd utilize os seguintes comandos:

 javac StringLength.java                        //para compilar  
 java StringLength                             //para executar  

Acredito que o resultado será algo desse tipo.

Figura 1 - Resultado do primeiro teste
Quando me deparei com este resultado fiquei me perguntando duas coisas: porque a string apareceu com os caracteres quebrados e porque o length é 5 ?? Através de muita pesquisa consegui a resposta para a primeira pergunta. O code page default da console do windows é o Multilingual (encoding Latin I) e possui o código 850, então caímos no mesmo problema que estudamos acima. Este code page não consegue processar caracteres com acentos. Então devemos mudar para o code page 65001 (UTF-8), através do comando chcp 65001.
Figura 2 - Resultado do segundo teste
Agora para piorar mais ainda nossa situação, crie uma classe Java com o código acima em um projeto do Eclipse IDE. Se o seu resultado foi o mesmo que o meu você também deve estar com a mesma dúvida.
Figura 3 - Resultado do terceiro teste
PORQUE OS LENGTHS DAS STRING SÃO DIFERENTES SE O CÓDIGO JAVA É O MESMO ?

Bem, eu ainda não tenho a resposta para esta pergunta, por isso conto com vocês. Se puderem me ajudar, deixe nos comentários a resposta para este problema. A minha opinião é que a IDE leva em conta o encoding type padrão do Java (UTF-16) na exibição em seu console interno e o console do windows não leva, mas isso é a minha opinião. Quero ouvir a de vocês.

Link do GitHub: https://github.com/PrecisoEstudarSempre/StringLength.git

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/

Referências:

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) - http://www.joelonsoftware.com/articles/Unicode.html

What is a Unicode code unit and a Unicode code point - https://coderanch.com/t/416952/java/java/Unicode-code-unit-Unicode-code

Documentação da classe String - https://docs.oracle.com/javase/7/docs/api/java/lang/String.html

Documentação oficial Unicode - http://www.unicode.org/versions/Unicode9.0.0/ch03.pdf#G7404

Unicode and .NET - http://csharpindepth.com/Articles/General/Unicode.aspx

encodings - different result between codePointCount and length - http://stackoverflow.com/questions/20162239/encodings-different-result-between-codepointcount-and-length

Comando Chcp - https://technet.microsoft.com/pt-br/library/bb490874.aspx

What encoding/code page is cmd.exe using - http://stackoverflow.com/questions/1259084/what-encoding-code-page-is-cmd-exe-using

What is the character encoding of String in Java? - http://stackoverflow.com/questions/4453269/what-is-the-character-encoding-of-string-in-java

Code Page Identifiers - https://msdn.microsoft.com/pt-br/library/windows/desktop/dd317756(v=vs.85).aspx
Leia Mais ››