Salve, pessoal! Para compensar minha relativa inatividade nos últimos tempos, volto hoje com um post mais denso, que acredito e espero que seja útil a toda a comunidade #SouDev.
Vou tratar sobre normalização de nomes próprios. Norma… o quê?
O problema
Algo que sempre achei horroroso em bancos de dados é a falta de padrão na capitalização (uso de letras maiúsculas e minúsculas) em nomes próprios – sejam de pessoas, logradouros ou localidades. Sobrinhos, geralmente, gravam no BD esses dados como foram coletados ou digitados, e programadores preguiçosos, no mais das vezes, limitam-se a aplicar um strtoupper() ou equivalente e entopem o cadastro com LETRAS MAIÚSCULAS. Na sinceridade, não sei qual das duas situações deixa o banco mais feio. O uso indiscriminado de letras maiúsculas, vale lembrar, cansa os olhos e dificulta a leitura após um certo tempo.
Já vi um caso em que a “saída” foi o uso de ucfirst() puro e simples. Menos mau, mas tão indolente quanto.
Fazer uma normalização, transformando “JOSÉ DA SILVA PEREIRA E SOUZA” em “José da Silva Pereira e Souza” não é uma tarefa difícil, desde que o desenvolvedor esteja atento a alguns detalhes.
A solução
Criei uma classe com um método estático em PHP (para não fugir ao paradigma da orientação a objetos) para propor minha solução. Expliquei, tanto quanto pude, todas as etapas nos comentários do código. Trata-se, em última instância, da implementação de um algoritmo relativamente simples, que não deve oferecer dificuldades para ser portado para outras linguagens. Se você utiliza PHP, de agora em diante aplique GUtils::normalizarNome($nome) e tenha um cadastro mais bonitinho e legível!
Se não, implemente a ideia em sua linguagem favorita.
<?php
/**
* Classe contêiner para métodos estáticos de utilidades variadas
*
* @author goncin (goncin ARROBA gmail PONTO com)
*/
class GUtils {
/**
* Constantes definidas para melhor legibilidade do código. O prefixo NN_ indica que
* seu uso está relacionado ao método público e estático normalizarNome().
*/
const NN_PONTO = '\.';
const NN_PONTO_ESPACO = '. ';
const NN_ESPACO = ' ';
const NN_REGEX_MULTIPLOS_ESPACOS = '\s+';
const NN_REGEX_NUMERO_ROMANO =
'^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$';
/**
* Normaliza o nome próprio dado, aplicando a capitalização correta de acordo
* com as regras e exceções definidas no código.
* POR UMA DECISÃO DE PROJETO, FORAM UTILIZADAS FUNÇÕES MULTIBYTE (MB_) SEMPRE
* QUE POSSÍVEL, PARA GARANTIR SUA USABILIDADE EM STRINGS UNICODE.
* @param string $nome O nome a ser normalizado
* @return string O nome devidamente normalizado
*/
public static function normalizarNome($nome) {
/*
* A primeira tarefa da normalização é lidar com partes do nome que
* porventura estejam abreviadas,considerando-se para tanto a existência de
* pontos finais (p. ex. JOÃO A. DA SILVA, onde "A." é uma parte abreviada).
* Dado que mais à frente dividiremos o nome em partes tomando em
* consideração o caracter de espaço (" "), precisamos garantir que haja um
* espaço após o ponto. Fazemos isso substituindo todas as ocorrências do
* ponto por uma sequência de ponto e espaço.
*/
$nome = mb_ereg_replace(self::NN_PONTO, self::NN_PONTO_ESPACO, $nome);
/*
* O procedimento anterior, ou mesmo a digitação errônea, podem ter
* introduzido espaços múltiplos entre as partes do nome, o que é totalmente
* indesejado. Para corrigir essa questão, utilizamos uma substituição
* baseada em expressão regular, a qual trocará todas as ocorrências de
* espaços múltiplos por espaços simples.
*/
$nome = mb_ereg_replace(self::NN_REGEX_MULTIPLOS_ESPACOS, self::NN_ESPACO,
$nome);
/*
* Isso feito, podemos fazer a capitalização "bruta", deixando cada parte do
* nome com a primeira letra maiúscula e as demais minúsculas. Assim,
* JOÃO DA SILVA => João Da Silva.
*/
$nome = mb_convert_case($nome, MB_CASE_TITLE, mb_detect_encoding($nome));
/*
* Nesse ponto, dividimos o nome em partes, para trabalhar com cada uma
* delas separadamente.
*/
$partesNome = mb_split(self::NN_ESPACO, $nome);
/*
* A seguir, são definidas as exceções à regra de capitalização. Como
* sabemos, alguns conectivos e preposições da língua portuguesa e de outras
* línguas jamais são utilizadas com a primeira letra maiúscula.
* Essa lista de exceções baseia-se na minha experiência pessoal, e pode ser
* adaptada, expandida ou mesmo reduzida conforme as necessidades de cada
* caso.
*/
$excecoes = array(
'de', 'di', 'do', 'da', 'dos', 'das', 'dello', 'della',
'dalla', 'dal', 'del', 'e', 'em', 'na', 'no', 'nas', 'nos', 'van', 'von',
'y'
);
for($i = 0; $i < count($partesNome); ++$i) {
/*
* Verificamos cada parte do nome contra a lista de exceções. Caso haja
* correspondência, a parte do nome em questão é convertida para letras
* minúsculas.
*/
foreach($excecoes as $excecao)
if(mb_strtolower($partesNome[$i]) == mb_strtolower($excecao))
$partesNome[$i] = $excecao;
/*
* Uma situação rara em nomes de pessoas, mas bastante comum em nomes de
* logradouros, é a presença de numerais romanos, os quais, como é sabido,
* são utilizados em letras MAIÚSCULAS.
* No site
* http://htmlcoderhelper.com/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression/,
* encontrei uma expressão regular para a identificação dos ditos
* numerais. Com isso, basta testar se há uma correspondência e, em caso
* positivo, passar a parte do nome para MAIÚSCULAS. Assim, o que antes
* era "Av. Papa João Xxiii" passa para "Av. Papa João XXIII".
*/
if(mb_ereg_match(self::NN_REGEX_NUMERO_ROMANO,
mb_strtoupper($partesNome[$i])))
$partesNome[$i] = mb_strtoupper($partesNome[$i]);
}
/*
* Finalmente, basta juntar novamente todas as partes do nome, colocando um
* espaço entre elas.
*/
return implode(self::NN_ESPACO, $partesNome);
}
}
const NN_PONTO = ‘\.’;
const NN_PONTO_ESPACO = ‘. ‘;
const NN_ESPACO = ‘ ‘;
const NN_REGEX_MULTIPLOS_ESPACOS = ‘\s+’;
const NN_REGEX_NUMERO_ROMANO =
‘^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$’;
Curtir isso:
Curtir Carregando...
Parabens, otimo artigo! E pode ter certeza, que vou usar e muito!
Obrigado! =D
Mais uma vez uma excelente classe. Com certeza, esta classe estará presente no meu framework pessoal que ainda desenvoverei
.
Parabéns e 1 abraço!
Opa, obrigado!
Quando seu framework pessoal estiver pronto, você vai jogá-lo no GitHub (se é que o GitHub ainda vai existir até lá)?
Vai depender da qualidade do código. Se ficar bom, sim.
Perfeito!!!
Parabéns
Muito bom! Meus parabéns! Até já favoritei o seu tweet. Mais um código de ótima qualidade.
Pingback: Aprendendo a pensar com expressões regulares « goncin@wordpress.com:~$ _
Opa,
Vou usar o conceito dessa classe para atender um problema que eu tenho que é o seguinte:
Nomes como
– O Auto da compadecida
– O Ultimo Samurai
devem ficar no formato:
- Auto da compadecida, O
- Ultimo Samurai, O
No mais,
parabéns o código está muito bem escrito/comentado.
Sucesso.
Olá, Emerson! Uma nova e boa aplicação para o código. Obrigado pela visita e pelo comentário
Aqui as letras acentuadas sao removidas…
mestresan,
Provavelmente, há um problema de codificação (encoding) das strings que você está tentando converter. Tente, ao menos, descobrir em qual codificação estão as suas strings.
uso iso-8859-1…
parece que a funcao so funciona legal com utf8 …
Se não me engano, a função ucwords() é mais rápida do que a mb_convert_case() e funciona com strings UTF-8. A chamada fica mais simplezinha também.
Já pensou em subir essa função (sem uma classe contêiner, no caso) pro GitHub pra que outros possam contribuir?
Bom trabalho!
Alexandre,
Não encontrei nada que me desse certeza de que ucwords() funcione com UTF-8.
Quanto ao GitHub, acabei de colocar o GUtils lá
Obrigado
Fiz uma pequena alteração conforme a sugestão do Alexandre e deu certo aki, funciona tanto em UTF8 como em ISO-8859-1
$nome = ucwords(strtolower($nome));
Clemerson,
Você fez testes com nomes acentuados?