quinta-feira, 27 de outubro de 2016

Avaliando blocos de texto com expressões regulares

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

Como todos sabemos, a leitura tradicional de um arquivo em qualquer linguagem de programação é a criação de um ponteiro em memória para uma representação do arquivo. Após isso, é aberta uma conexão de leitura/escrita e a partir daí toda a mágica que já conhecemos acontece. Se o seu programa deseja ler ou escrever informações do ou no arquivo, ele consegue. Contudo, devemos agora nos concentrar mais no processo de leitura de arquivos.

Geralmente, ele acontece linha a linha e a linguagem de programação usada para tal tarefa oferece métodos para recuperar esta em forma de string. Com o dado em nossas mãos podemos fazer o que quiser, por exemplo: aplicar uma expressão regular, concatenar dados, realizar análises e etc. Se concentre no primeiro exemplo, a aplicação de uma expressão regular.

Este assunto aqui no blog não é nenhuma novidade, pois já estudamos em outros post diversas expressões regulares ou regexs para infinitas utilidades. Mas, e se desejássemos capturar um bloco de texto oriundo de um arquivo através delas ? Como faríamos ?

A princípio parece uma tarefa que dá um nó na cabeça, pois como já citamos nos parágrafos acima a leitura acontece linha a linha, e isso não vai mudar. Então, precisamos de uma lógica boa que consiga capturar este bloco através de várias expressões passadas para ele.

Para a solução deste problema, utilizarei mais uma vez a linguagem de programação Java pois possuo um alto nível de afinidade com ela, mas esta solução é estendível para qualquer uma.

Antes de montar o algoritmo precisamos chegar a um acordo de como será nossa estrutura de regexs. Para que um bloco inteiro, onde quem define a quantidade de linhas desse bloco é a sua necessidade, possa ser capturado, precisamos mapear como é cada linha. Então, para fins de estudos utilizaremos um arquivo XML, pois este tipo de arquivo possui uma estrutura hierárquica que neste momento nos facilita a enxergar a solução. Então, no fim teremos.

Arquivo precisoestudarsempre.xml

 precisoestudarsempre.xml  
   
 <blog>  
      <nome>Preciso Estudar Sempre</nome>  
      <email>precisoestudarsempre@gmail.com</email>  
      <descricao>Melhor blog do mundo :)</descricao>  
 </blog>  

Nossas expressões regulares:

 private final String[] arrayOfRegex = {"<nome>([a-zA-Z]+\s*)*</nome>",  
                                              "<email>.*</email>",  
                                              "<descricao>.*</descricao>"};  

IMPORTANTE: É importante deixar claro aqui que neste post não destrincharei cada regex como já fiz em outros post. Nosso foco aqui é o algoritmo e não o estudo de expressões. Deixarei no fim do post vários links para outros post aqui do blog onde já expliquei várias vezes como e o que são as expressões regulares.

Agora que já montamos o nosso arquivo e sua respectiva estrutura de regexs, já temos o necessário para construir nosso algoritmo.

 private void evaluateTextBlockRegex(BufferedReader bf){  
   try{  
     int regexCounter;  
     int lineNumber;  
     regexCounter = lineNumber = 0;  
     boolean isMatchedRegex = true;  
     Pattern pattern = null;  
             
     while (bf.ready()) {  
       String line = bf.readLine();  
       lineNumber++;          
   
       if(regexCounter == arrayOfRegex.length){  
         //só para pular linha  
         System.out.println();  
         regexCounter = 0;            
       }                  
       while (isMatchedRegex) {  
         String regex = arrayOfRegex[regexCounter];  
           
         try {  
           pattern = Pattern.compile(regex);  
           Matcher matcher = pattern.matcher(line);  
           isMatchedRegex = matcher.find();                                          
           if(isMatchedRegex){  
             //exibo a linha capturada  
             System.out.println("Linha " + lineNumber + " capturada: " + line);                
             regexCounter++;  
             break;  
           }  
         } catch (java.util.regex.PatternSyntaxException pse) {  
           //talvez que a regex não seja compilada  
           isMatchedRegex = false;  
         }  
       }          
       if(!isMatchedRegex){            
         isMatchedRegex = true;  
         regexCounter = 0;  
       }          
     }  
   } catch(java.io.IOException e){  
     System.out.println("An I/O error occurs!");  
   }  
 }  

O funcionamento dele é o seguinte: o arquivo é lido linha após linha, e para cada uma é testada sempre a primeira regex. Caso esta tenha sucesso, o contador regexCounter é incrementado e uma nova linha é carregada na variável line, e assim o algoritmo testa a segunda regex, repetindo este processo até o array de regex chegar ao seu fim. Para que um bloco de texto seja capturado a correspondência entre linhas do arquivo e expressões regulares devem ser linha a linha, ou seja, a primeira regex corresponde a primeira linha do bloco e assim sucessivamente. A figura 1 esclarece tal relacionamento.
Figura 1 - Relacionamento linha - regex
Após todas as linhas terem sido relacionados com suas respectivas regexs, regexCounter é zerado para que o aglomerado de regex procure novamente por novas correspondências. Este é necessário para que seja possível acessar a próxima expressão do array. A flag booleana isMatchedRegex é necessária pois podem existir casos onde somente algumas linhas correspondem e outras não. Quando uma que não corresponde for avaliada, a análise precisa ser reiniciada, ou seja, é iniciada mais uma vez a procura por um bloco de texto compatível.

Todo este procedimento citado neste dois últimos parágrafos se repetem até o final do arquivo.

Para um bom conhecedor de Java uma pergunta pode surgir. Porque não usar a opção multiline da classe Pattern ? Sim, é verdade, existe uma opção pronta para este tipo de avaliação. Contudo, como no nosso caso estamos recuperando texto direto de um arquivo seria necessário transformar todo o arquivo em uma única string, para depois poder usar tal artifício. Tal processo pode apresentar problemas de tamanho de memória visto que o arquivo pode ser muito grande. Um outro ponto que torna nosso algoritmo mais interessante é que ele se encaixa mais facilmente em abordagens tradicionais da leitura de arquivos. Através de pesquisa é possível coletar relatos que esta opção traz problemas para programas que operam em multiplataformas, visto que cada plataforma tem um caractere de quebra de linha próprio. Lembra que já falamos disso aqui ?

Como eu sempre digo, não existem balas de prata. Não existe uma única solução correta. Devemos medir os prós e contras, e assim tomar nossa decisão.

Bem amigos, acabamos mais um post. Espero que vocês tenham gostado.

Baixe o código-fonte
Link do GitHub: https://github.com/PrecisoEstudarSempre/EvaluateTextBlockRegex.git
Link no Dropbox: https://www.dropbox.com/sh/trgchzdjr198ja7/AABCqIPsGPe15OC43IKKxiAga?dl=0
Link no GoogleDrive: https://drive.google.com/drive/folders/0BzDmhBY6luU6ZUR1d0tXRk91QkU?usp=sharing

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



Non-break space, já ouviu falar ? - Regex em Java - http://precisoestudarsempre.blogspot.com.br/2015/11/non-break-space-ja-ouviu-falar-regex-em.html



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 ››

sábado, 1 de outubro de 2016

Desenvolvi o WriteYourOwnGraph

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

Vou começar esse post com uma ótima notícia. Desenvolvi uma ferramenta gratuita para o desenvolvimento de grafos.
Figura 1 - A reação de vocês

Meu planejamento para esta primeira versão dessa nova empreitada é disponibilizar o código construído no GitHub e depois transformá-lo em uma ferramenta web, onde você de qualquer parte do mundo possa acessar, construir seu grafo e exportar ele em forma de imagem, HTML, etc.

Mas da onde veio a motivação para construir o WriteYourOwnGraph ? Como tudo começou ? Muito tempo atrás estudei para uma prova de certificação de HTML 5 e acabei me dando mal, mas derrotas para um outro dia. Um dia lembrei que a especificação do HTML 5 possui diversas novas APIs e uma delas é para a criação de desenhos vetoriais, chamada SVG. Com ela é possível a criação de inúmeras formas geométricas. Agora, tudo o que eu precisava saber era como construir nós e arestas com ela. Para minha sorte, tais elementos se resumem a pequenas circunferências e linhas retas, respectivamente. Então, só o que eu precisava fazer era estudar a especificação da API e tudo ganharia forma.

Feito isso, a ferramenta WriteYourOwnGraph nasceu e adquiriu sua humilde versão 1.0 finalizada com sucesso. Pelo fato de ter sido construída com HTML 5, Javascript + JQuery e CSS é rápida e não é necessário instalar nada para executá-la na sua máquina, só basta o seu navegador. Então, é com muita felicidade que lhes apresento a primeira ferramenta desenvolvida pelo blog Preciso Estudar Sempre para o estudo e construção de grafos.
Figura 2 - Nossa que maravilha
Para executar a ferramenta, abra a página graph.html.

Se você usou e sentiu falta de algo, por favor não poupe palavras e escreva tudo o que pensa nos comentários. Faça parte da evolução.

Para baixar o código-fonte, clique aqui.

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

Lista de figuras:
Figura 1 -  http://i.giphy.com/5Zesu5VPNGJlm.gif
Figura 2 - http://i.giphy.com/k7xgyFqsruqqc.gif
Leia Mais ››