Eu recentemente postou uma resposta para esta pergunta em códigos postais do Reino Unido para a linguagem R . Descobri que o padrão de regex do governo do Reino Unido está incorreto e falha ao validar corretamente alguns códigos postais. Infelizmente, muitas das respostas aqui são baseadas neste padrão incorreto.
Vou descrever alguns desses problemas abaixo e fornecer uma expressão regular revisada que realmente funciona.
Nota
Minha resposta (e expressões regulares em geral):
- Valida apenas os formatos de código postal .
- Não garante que um código postal exista legitimamente .
Se você não se importa com a regex incorreta e deseja apenas pular para a resposta, role para baixo até a seção Resposta .
The Bad Regex
As expressões regulares nesta seção não devem ser usadas.
Este é o regex com falha que o governo do Reino Unido forneceu aos desenvolvedores (não sabe quanto tempo esse link ficará ativo, mas você pode vê-lo na documentação da Transferência de dados em massa ):
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))[0-9][A-Za-z]{2})$
Problemas
Problema 1 - Copiar / Colar
Veja regex em uso aqui .
Como muitos desenvolvedores provavelmente copiam / colam código (especialmente expressões regulares) e os colam esperando que funcionem. Embora isso seja ótimo em teoria, ele falha neste caso específico, porque copiar / colar deste documento realmente altera um dos caracteres (um espaço) para um caractere de nova linha, como mostrado abaixo:
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))
[0-9][A-Za-z]{2})$
A primeira coisa que a maioria dos desenvolvedores fará é apagar a nova linha sem pensar duas vezes. Agora, a regex não corresponderá aos códigos postais com espaços (exceto o GIR 0AA
código postal).
Para corrigir esse problema, o caractere de nova linha deve ser substituído pelo caractere de espaço:
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$
^
Problema 2 - Limites
Veja regex em uso aqui .
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$
^^ ^ ^ ^^
O código postal regex ancora incorretamente o regex. Qualquer pessoa que use esse regex para validar códigos postais pode se surpreender se um valor como o fooA11 1AA
passar. Isso ocorre porque eles ancoraram o início da primeira opção e o final da segunda opção (independentemente uma da outra), conforme apontado no regex acima.
O que isso significa é que ^
(afirma a posição no início da linha) funciona apenas na primeira opção ([Gg][Ii][Rr] 0[Aa]{2})
; portanto, a segunda opção validará todas as strings que terminam em um código postal (independentemente do que vem antes).
Da mesma forma, a primeira opção não está ancorada no final da linha $
, GIR 0AAfoo
também é aceita.
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))[0-9][A-Za-z]{2})$
Para corrigir esse problema, as duas opções devem ser agrupadas em outro grupo (ou grupo que não captura) e as âncoras colocadas em torno dele:
^(([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2}))$
^^ ^^
Problema 3 - Conjunto de caracteres inadequado
Veja regex em uso aqui .
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$
^^
O regex está ausente -
aqui para indicar um intervalo de caracteres. Tal como está, se um código postal estiver no formato ANA NAA
(onde A
representa uma letra e N
um número) e começar com algo diferente de A
ou Z
, falhará.
Isso significa que corresponderá A1A 1AA
e Z1A 1AA
, mas não B1A 1AA
.
Para corrigir esse problema, o caractere -
deve ser colocado entre A
e Z
no respectivo conjunto de caracteres:
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$
^
Problema 4 - Conjunto de caracteres opcional errado
Veja regex em uso aqui .
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$
^
Juro que eles nem testaram isso antes de publicá-lo na web. Eles tornaram o conjunto de caracteres errado opcional. Eles fizeram a [0-9]
opção na quarta subopção da opção 2 (grupo 9). Isso permite que o regex corresponda a códigos postais formatados incorretamente, como AAA 1AA
.
Para corrigir esse problema, torne opcional a próxima classe de caractere (e subseqüentemente faça o conjunto [0-9]
corresponder exatamente uma vez):
^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9][A-Za-z]?)))) [0-9][A-Za-z]{2})$
^
Problema 5 - Desempenho
O desempenho nesse regex é extremamente ruim. Primeiro, eles colocaram a opção de padrão menos provável de corresponder GIR 0AA
no início. Quantos usuários provavelmente terão esse código postal em comparação com qualquer outro código postal; provavelmente nunca? Isso significa que toda vez que a regex é usada, ela deve esgotar essa opção antes de prosseguir para a próxima opção. Para ver como o desempenho é afetado, verifique o número de etapas que a regex original executou (35) na mesma regex após ter invertido as opções (22).
O segundo problema com o desempenho se deve à maneira como toda a regex está estruturada. Não faz sentido voltar atrás em cada opção se uma falhar. A maneira como o regex atual é estruturado pode ser bastante simplificada. Eu forneço uma correção para isso na seção Resposta .
Problema 6 - Espaços
Veja regex em uso aqui
Isso pode não ser considerado um problema , por si só, mas gera preocupação para a maioria dos desenvolvedores. Os espaços no regex não são opcionais, o que significa que os usuários que inserem seus códigos postais devem colocar um espaço no código postal. Essa é uma solução fácil, basta adicionar ?
após os espaços para torná-los opcionais. Consulte a seção Resposta para uma correção.
Responda
1. Corrigindo o Regex do governo do Reino Unido
A correção de todos os problemas descritos na seção Problemas e a simplificação do padrão produz o seguinte padrão, mais curto e conciso. Também podemos remover a maioria dos grupos, pois estamos validando o código postal como um todo (não partes individuais):
Veja regex em uso aqui
^([A-Za-z][A-Ha-hJ-Yj-y]?[0-9][A-Za-z0-9]? ?[0-9][A-Za-z]{2}|[Gg][Ii][Rr] ?0[Aa]{2})$
Isso pode ser reduzido ainda mais, removendo todos os intervalos de um dos casos (maiúsculas ou minúsculas) e usando um sinalizador que não diferencia maiúsculas de minúsculas. Nota : Alguns idiomas não têm um, portanto, use o mais longo acima. Cada idioma implementa o sinalizador de distinção entre maiúsculas e minúsculas de maneira diferente.
Veja regex em uso aqui .
^([A-Z][A-HJ-Y]?[0-9][A-Z0-9]? ?[0-9][A-Z]{2}|GIR ?0A{2})$
Shorter novamente substituindo [0-9]
com \d
(se o seu motor regex suporta):
Veja regex em uso aqui .
^([A-Z][A-HJ-Y]?\d[A-Z\d]? ?\d[A-Z]{2}|GIR ?0A{2})$
2. Padrões simplificados
Sem garantir caracteres alfabéticos específicos, é possível usar o seguinte (lembre-se das simplificações de 1. A correção do Regex do governo do Reino Unido também foi aplicada aqui):
Veja regex em uso aqui .
^([A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}|GIR ?0A{2})$
E ainda mais se você não se importa com o caso especial GIR 0AA
:
^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$
3. Padrões complicados
Eu não sugeriria a verificação excessiva de um código postal, pois novas áreas, distritos e subdistritos podem aparecer a qualquer momento. O que eu sugiro fazer potencialmente é o suporte adicional para casos extremos. Alguns casos especiais existem e são descritos neste artigo da Wikipedia .
Aqui estão as expressões regulares complexas que incluem as subseções de 3. (3.1, 3.2, 3.3).
Em relação aos padrões em 1. Fixing Regex do governo do Reino Unido :
Veja regex em uso aqui
^(([A-Z][A-HJ-Y]?\d[A-Z\d]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?\d[A-Z]{2}|BFPO ?\d{1,4}|(KY\d|MSR|VG|AI)[ -]?\d{4}|[A-Z]{2} ?\d{2}|GE ?CX|GIR ?0A{2}|SAN ?TA1)$
E em relação a 2. Padrões simplificados :
Veja regex em uso aqui
^(([A-Z]{1,2}\d[A-Z\d]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?\d[A-Z]{2}|BFPO ?\d{1,4}|(KY\d|MSR|VG|AI)[ -]?\d{4}|[A-Z]{2} ?\d{2}|GE ?CX|GIR ?0A{2}|SAN ?TA1)$
3.1 Territórios ultramarinos britânicos
O artigo da Wikipedia afirma atualmente (alguns formatos ligeiramente simplificados):
AI-1111
: Anguila
ASCN 1ZZ
: Ilha da Ascensão
STHL 1ZZ
: Santa Helena
TDCU 1ZZ
: Tristan da Cunha
BBND 1ZZ
: Território Britânico do Oceano Índico
BIQQ 1ZZ
: Território Antártico Britânico
FIQQ 1ZZ
: Ilhas Malvinas
GX11 1ZZ
: Gibraltar
PCRN 1ZZ
: Ilhas Pitcairn
SIQQ 1ZZ
: Ilhas Geórgia do Sul e Sandwich do Sul
TKCA 1ZZ
: Ilhas Turcas e Caicos
BFPO 11
: Akrotiri e Dhekelia
ZZ 11
& GE CX
: Bermuda (de acordo com este documento )
KY1-1111
: Ilhas Cayman (de acordo com este documento )
VG1111
: Ilhas Virgens Britânicas (de acordo com este documento )
MSR 1111
: Montserrat (de acordo com este documento )
Um regex abrangente para corresponder apenas aos Territórios Ultramarinos Britânicos pode ser assim:
Veja regex em uso aqui .
^((ASCN|STHL|TDCU|BBND|[BFS]IQQ|GX\d{2}|PCRN|TKCA) ?\d[A-Z]{2}|(KY\d|MSR|VG|AI)[ -]?\d{4}|(BFPO|[A-Z]{2}) ?\d{2}|GE ?CX)$
3.2 Correios das forças britânicas
Embora tenham sido alterados recentemente para melhor alinhar-se ao sistema de código postal britânico para BF#
(onde #
representa um número), eles são considerados códigos postais alternativos opcionais . Esses códigos postais seguem (ed) o formato de BFPO
, seguido por 1 a 4 dígitos:
Veja regex em uso aqui
^BFPO ?\d{1,4}$
3.3 Papai Noel?
Há outro caso especial com o Papai Noel (como mencionado em outras respostas): SAN TA1
é um código postal válido. Um regex para isso é muito simples:
^SAN ?TA1$