JavaRush /Blogue Java /Random-PT /RegEx: 20 passos curtos para dominar expressões regulares...
Artur
Nível 40
Tallinn

RegEx: 20 passos curtos para dominar expressões regulares. Parte 4

Publicado no grupo Random-PT
RegEx: 20 passos curtos para dominar expressões regulares. Parte 1 RegEx: 20 passos curtos para dominar expressões regulares. Parte 2 20 passos curtos para dominar expressões regulares. Parte 3 Esta parte final, no meio, abordará coisas que são usadas principalmente por mestres em expressões regulares. Mas o material das partes anteriores foi fácil para você, certo? Isso significa que você pode manusear esse material com a mesma facilidade! Original aqui RegEx: 20 passos curtos para dominar expressões regulares.  Parte 4 - 1 <h2>Etapa 16: grupos sem captura (?:)</h2> RegEx: 20 passos curtos para dominar expressões regulares.  Parte 4 - 2Nos dois exemplos da etapa anterior, estávamos capturando texto que realmente não precisávamos. Na tarefa File Sizes, capturamos os espaços antes do primeiro dígito dos tamanhos dos arquivos, e na tarefa CSV, capturamos as vírgulas entre cada token. Não precisamos capturar esses caracteres, mas precisamos usá-los para estruturar nossa expressão regular. Estas são opções ideais para usar um grupo sem capturar arquivos (?:). Um grupo sem captura faz exatamente o que parece - permite que caracteres sejam agrupados e usados ​​em expressões regulares, mas não os captura em um grupo numerado:
pattern: (?:")([^"]+)(?:") 
string: Eu só quero "o texto dentro dessas aspas" .
correspondências:             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
grupo:                 11111111111111111111111111111    
( Exemplo ) A expressão regular agora corresponde ao texto citado, bem como aos próprios caracteres de aspas, mas o grupo de captura capturou apenas o texto citado. Por que devemos fazer isso? A questão é que a maioria dos mecanismos de expressões regulares permitem recuperar texto de grupos de captura definidos em suas expressões regulares. Se pudermos cortar os caracteres extras que não precisamos sem incluí-los em nossos grupos de captura, será mais fácil analisar e manipular o texto posteriormente. Veja como limpar o analisador CSV da etapa anterior:
padrão: (?:^|,)\s*(?:\"([^",]*)\"|([^", ]*)) 
string:   a , " b ", " cd ", e , f , " gh ", dfgi ,, k , "", l 
corresponde a: ^ ^ ^^^ ^ ^ ^^^ ^^^^ ^ ^ 
grupo:    2 1 111 2 2 111 2222 2 2    
( Exemplo ) Há algumas coisas a <mark>observar aqui:</mark> Primeiro, não estamos mais capturando vírgulas, pois alteramos o grupo de captura (^|,)para um grupo sem captura (?:^|,). Segundo, aninhamos o grupo de captura dentro do grupo de não captura. Isso é útil quando, por exemplo, você precisa que um grupo de caracteres apareça em uma ordem específica, mas só se preocupa com um subconjunto desses caracteres. Em nosso caso, precisávamos que caracteres sem aspas e sem vírgulas [^",]*aparecessem entre aspas, mas na verdade não precisávamos dos caracteres de aspas em si, portanto, eles não precisavam ser capturados. Finalmente, <mark>observe</mark> que no exemplo acima também há uma correspondência de comprimento zero entre os caracteres ke l. As aspas ""são a subsequência pesquisada, mas não há caracteres entre as aspas, portanto a subsequência correspondente não contém caracteres (comprimento zero). <h3>Vamos consolidar nosso conhecimento? Aqui estão duas tarefas e meia que nos ajudarão com isso:</h3> Usando grupos sem captura (e grupos de captura, classes de caracteres, etc.), escreva uma expressão regular que capture apenas tamanhos de arquivo formatados corretamente na linha abaixo :
padrão:
sequência:   6,6 KB 1..3 KB 12 KB 5G 3,3 MB KB .6,2 TB 9 MB .
correspondências: ^^^^^ ^^^^^ ^^^^^^ ^^^^ 
grupo:    11111 1111 11111 111    
( Solução ) As tags de abertura HTML começam <e terminam com >. As tags de fechamento HTML começam com uma sequência de caracteres </e terminam com o caractere >. O nome da tag está contido entre esses caracteres. Você pode escrever uma expressão regular para capturar apenas os nomes nas tags a seguir? (Você pode resolver esse problema sem usar grupos que não sejam de captura. Tente resolver isso de duas maneiras! Uma vez com grupos e outra sem.)
padrão:
string:   <p> </span> <div> </kbd> <link> 
corresponde a: ^^^ ^^^^^^ ^^^^^ ^^^^^^ ^^^^^^ 
grupo:    1 1111 111 111 1111    
( Solução usando grupos sem captura ) ( Solução sem usar grupos sem captura ) <h2>Etapa 17: Backlinks \Ne grupos de captura nomeados</h2> RegEx: 20 passos curtos para dominar expressões regulares.  Parte 4 - 3Embora eu tenha avisado na introdução que tentar criar um analisador HTML usando expressões regulares geralmente leva à dor de cabeça, este último exemplo é uma boa transição para outro recurso (às vezes) útil da maioria das expressões regulares: referências anteriores. Backlinks são como grupos repetidos onde você pode tentar capturar o mesmo texto duas vezes. Mas eles diferem em um aspecto importante: eles capturarão apenas o mesmo texto, caractere por caractere. Embora um grupo de repetição nos permita capturar algo assim:
padrão: (he(?:[az])+) 
string:   heyabcdefg hey heyo heyellow heyyyyyyyyy 
corresponde a: ^^^^^^^^^^ ^^^ ^^^^ ^^^^^^^^ ^^^ ^^^^^^^^ 
grupo:    1111111111 111 1111 11111111 11111111111    
( Exemplo ) ... então o backlink corresponderá apenas a isto:
padrão: (he([az])(\2+)) 
string: heyabcdefg hey heyo heyellow heyyyyyyyyy 
corresponde a:                              ^^^^^^^^^^^^ 
grupo:                                 11233333333    
( Exemplo ) Grupos de captura repetidos são úteis quando você deseja corresponder o mesmo padrão repetidamente, enquanto backlinks são bons quando você deseja corresponder o mesmo texto. Por exemplo, poderíamos usar um backlink para tentar encontrar tags HTML de abertura e fechamento correspondentes:
padrão: <(\w+)[^>]*>[^<]+<\/\1> 
string:   <span style="color: red">ei</span> 
correspondências: ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
grupo:    1111    
( Exemplo ) <mark>Observe</mark> que este é um exemplo extremamente simplificado e eu recomendo fortemente que você não tente escrever um analisador HTML baseado em expressões regulares. Esta é uma sintaxe muito complexa e provavelmente o deixará doente. Grupos de captura nomeados são muito semelhantes aos backlinks, então irei abordá-los brevemente aqui. A única diferença entre referências anteriores e um grupo de captura nomeado é que... um grupo de captura nomeado tem um nome:
padrão: <(?<tag>\w+)[^>]*>[^<]+<\/(?P=tag)></tag> 
string:   <span style="color: red">ei< /span> 
correspondências: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
grupo:    1111    
( Exemplo ) Você pode criar um grupo de captura nomeado usando a sintaxe (?<nome>...) ou (?'nome'...) (expressão regular compatível com .NET) ou com esta sintaxe (?P<nome>. ..) ou (?P'name'...) (expressão regular compatível com Python). Como estamos usando PCRE (Expressão Regular Compatível com Perl) que suporta ambas as versões, podemos usar qualquer uma aqui. (Java 7 copiou a sintaxe do .NET, mas apenas a versão dos colchetes angulares. Nota do tradutor) Para repetir um grupo de captura nomeado posteriormente em uma expressão regular, usamos \<kname> ou \k'name' (.NET) ou (? P = nome) (Python). Novamente, o PCRE oferece suporte a todas essas opções diferentes. Você pode ler mais sobre grupos de captura nomeados aqui , mas isso é o que você realmente precisa saber sobre eles. <h3>Tarefa para nos ajudar:</h3> Use backlinks para me ajudar a lembrar... ummm... o nome dessa pessoa.
padrão:
string: "Olá, meu nome é Joe." [mais tarde] "Qual é o nome daquele cara? Joe ?"
correspondências:        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ 
grupo:                  111    
( Solução ) <h2>Etapa 18: olhar para frente e para trás</h2> RegEx: 20 passos curtos para dominar expressões regulares.  Parte 4 - 4Agora vamos nos aprofundar em alguns dos recursos avançados das expressões regulares. Eu uso tudo até a etapa 16 com bastante frequência. Mas essas últimas etapas são apenas para pessoas que usam regex muito a sério para combinar expressões muito complexas. Em outras palavras, mestres em expressões regulares. “Olhar para frente” e “olhar para trás” podem parecer bastante complicados, mas na verdade não são tão complicados. Eles permitem que você faça algo semelhante ao que fizemos anteriormente com grupos sem captura - verifique se há algum texto imediatamente antes ou imediatamente depois do texto real que queremos corresponder. Por exemplo, suponha que queiramos corresponder apenas os nomes de coisas que as pessoas gostam, mas apenas se elas estiverem entusiasmadas com isso (apenas se terminarem a frase com um ponto de exclamação). Poderíamos fazer algo como:
padrão: (\w+)(?=!) 
string: Eu gosto de mesa. Eu aprecio o grampeador. Adoro luminária !
correspondências:                                           ^^^^ 
grupo:                                              1111    
( Exemplo ) Você pode ver como o grupo de captura acima (\w+), que geralmente corresponde a qualquer uma das palavras da passagem, corresponde apenas à palavra lâmpada. A antecipação positiva (?=!)significa que só podemos corresponder sequências que terminam em !, mas na verdade não correspondemos ao próprio caractere de ponto de exclamação. Esta é uma distinção importante porque com grupos que não capturam estamos combinando o personagem, mas não o capturando. Com lookaheads e lookbehinds, usamos um caractere para construir nossa expressão regular, mas nem sequer o comparamos com ele mesmo. Podemos combiná-lo mais tarde em nossa expressão regular. Existem quatro tipos de lookaheads e lookbehinds: lookahead positivo (?=...), lookahead negativo (?!...), lookahead positivo (?<=...) e lookahead negativo (?<!. ..) . Eles fazem o que parecem - lookahead e lookbehind positivos permitem que o mecanismo de expressão regular continue correspondendo apenas quando o texto contido no lookahead/lookbehind realmente corresponder. Lookahead e lookbehind negativos fazem o oposto - eles permitem que o regex corresponda apenas quando o texto contido no lookahead/lookbehind não corresponde. Por exemplo, queremos combinar nomes de métodos apenas em uma cadeia de sequências de métodos, e não no objeto em que eles operam. Neste caso, cada nome de método deve ser precedido por um .. Uma expressão regular usando uma simples retrospectiva pode ajudar aqui:
padrão: (?<=\.)(\w+) 
string: meuArray. flatMap.agregado.summarise.print !
correspondências:         ^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^^^^^ 
grupo:            1111111 111111111 111111111 11111    
( Exemplo ) No texto acima, combinamos qualquer sequência de caracteres de palavras \w+, mas apenas se forem precedidos pelo caractere .. Poderíamos conseguir algo semelhante usando grupos sem captura, mas o resultado é um pouco mais confuso:
padrão: (?:\.)(\w+) 
string: myArray .flatMap.agregado.summarise.print !
correspondências:        ^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^^^^^ 
grupo:            1111111 111111111 111111111 11111    
( Exemplo ) Mesmo sendo mais curto, ele corresponde a caracteres que não precisamos. Embora este exemplo possa parecer trivial, olhar para frente e para trás pode realmente nos ajudar a limpar nossas expressões regulares. <h3>Faltam muito poucos até o final! As 2 tarefas a seguir nos deixarão um passo mais perto disso:</h3> Lookbehind negativo (?<!...) permite que o mecanismo de expressão regular continue tentando encontrar uma correspondência apenas se o texto contido dentro do lookbehind negativo não for exibido até o resto do texto, com o qual você precisa encontrar uma correspondência. Por exemplo, poderíamos usar uma expressão regular para corresponder apenas aos sobrenomes das mulheres que participam de uma conferência. Para fazer isso, gostaríamos de ter certeza de que o sobrenome da pessoa não seja precedido por um Mr.. Você pode escrever uma expressão regular para isso? (Os sobrenomes podem ter pelo menos quatro caracteres.)
padrão:
corda: Sr. Marrom, Sra. Smith , Sra. Jones , Senhorita Daisy , Sr. Verde
correspondências:                ^^^^^ ^^^^^ ^^^^^ 
grupo:                   11111 11111 11111    
( Solução ) Digamos que estamos limpando um banco de dados e temos uma coluna de informações que representa porcentagens. Infelizmente, algumas pessoas escreveram números como valores decimais no intervalo [0,0, 1,0], enquanto outras escreveram porcentagens no intervalo [0,0%, 100,0%], e outras ainda escreveram valores percentuais, mas esqueceram o sinal literal de porcentagem %. Usando lookahead negativo (?!...), você pode marcar apenas os valores que deveriam ser porcentagens, mas estão faltando dígitos %? Estes devem ser valores estritamente maiores que 1,00, mas sem trailing %. (Nenhum número pode conter mais de dois dígitos antes ou depois do ponto decimal.) <mark>Observe</mark> que esta solução é extremamente difícil . Se você conseguir resolver esse problema sem olhar minha resposta, então você já tem grandes habilidades em expressões regulares!
padrão:
string: 0,32 100,00 5,6 0,27 98% 12,2% 1,01 0,99% 0,99 13,13 1,10 
correspondências:      ^^^^^^ ^^^ ^^^^ ^^^^^ ^^^^ 
grupo:         111111 111 1111 11111 1111    
( Solução ) <h2>Etapa 19: Condições em Expressões Regulares</h2> RegEx: 20 passos curtos para dominar expressões regulares.  Parte 4 - 5Chegamos agora ao ponto em que a maioria das pessoas não usará mais expressões regulares. Cobrimos provavelmente 95% dos casos de uso de expressões regulares simples, e tudo o que é feito nas etapas 19 e 20 normalmente é feito por uma linguagem de manipulação de texto com mais recursos, como awk ou sed (ou uma linguagem de programação de uso geral). Dito isto, vamos em frente, só para você saber o que uma expressão regular pode realmente fazer. Embora as expressões regulares não sejam Turing completas , alguns mecanismos de expressões regulares oferecem recursos muito semelhantes a uma linguagem de programação completa. Um desses recursos é a "condição". Condicionais Regex permitem instruções if-then-else, onde o ramo escolhido é determinado pelo "olhar para frente" ou "olhar para trás" que aprendemos na etapa anterior. Por exemplo, você pode querer corresponder apenas entradas válidas em uma lista de datas:
padrão: (?<=Feb )([1-2][0-9])|(?<=Mar )([1-2][0-9]|3[0-1]) 
string: Datas trabalhadas : 28 de fevereiro , 29 de fevereiro , 30 de fevereiro, 30 de março , 31 de março  
partidas:                   ^^ ^^ ^^ ^^ 
grupo:                      11 11 22 22    
( Exemplo ) <mark>Observe</mark> que os grupos acima também são indexados por mês. Poderíamos escrever uma expressão regular para todos os 12 meses e capturar apenas as datas válidas, que seriam então combinadas em grupos indexados por mês do ano. O texto acima usa uma espécie de estrutura do tipo if que só procurará correspondências no primeiro grupo se "fevereiro" preceder um número (e da mesma forma para o segundo). Mas e se quiséssemos usar processamento especial apenas para fevereiro? Algo como "se o número for precedido de "fevereiro", faça isso, caso contrário, faça outra coisa". Veja como os condicionais fazem isso:
padrão: (?(?<=Feb )([1-2][0-9])|([1-2][0-9]|3[0-1])) 
string: Datas de trabalho: 28 de fevereiro , 29 de fevereiro , 30 de fevereiro, 30 de março , 31 de março  
partidas:                   ^^ ^^ ^^ ^^ 
grupo:                      11 11 22 22    
( Exemplo ) A estrutura if-then-else se parece com (?(If)then|else), onde (if) é substituído por "olhar para frente" ou "olhar para trás". No exemplo acima, (if) é escrito como (?<=Feb). Você pode ver que combinamos datas maiores que 29, mas somente se elas não seguirem "fevereiro". Usar lookbehinds em expressões condicionais é útil se você quiser garantir que a correspondência seja precedida por algum texto. Condicionais lookahead positivos podem ser confusos porque a condição em si não corresponde a nenhum texto. Portanto, se você quiser que a condição if tenha um valor, ela deve ser comparável ao lookahead como abaixo:
padrão: (?(?=exato)exato|else)wo 
string: exato else exactwo elsewo  
corresponde:            ^^^^^^^ ^^^^^^
( Exemplo ) Isso significa que condicionais antecipadas positivas são inúteis. Você verifica se o texto está na frente e fornece um padrão correspondente a ser seguido quando estiver. A expressão condicional não nos ajuda em nada aqui. Você também pode substituir o acima por uma expressão regular mais simples:
padrão: (?:exato|else)wo 
string: exato else exactwo elsewo  
corresponde:            ^^^^^^^ ^^^^^^
( Exemplo ) Portanto, a regra prática para expressões condicionais é: teste, teste e teste novamente. Caso contrário, as soluções que você considera óbvias irão falhar das maneiras mais emocionantes e inesperadas :) <h3>Aqui chegamos ao último bloco de tarefas que nos separa da 20ª etapa final:</h3> Escreva uma expressão regular que usa expressão condicional lookahead negativa para testar se a próxima palavra começa com uma letra maiúscula. Nesse caso, pegue apenas uma letra maiúscula e depois as letras minúsculas. Caso contrário, pegue qualquer caractere de palavra.
padrão:
string:   Jones Smith 9sfjn Hobbes 23r4tgr9h CSV Csv vVv 
corresponde: ^^^^^ ^^^^^ ^^^^^ ^^^^^^ ^^^^^^^^^ ^^^ ^^^ 
grupo:    22222 22222 11111 222222 111111111 222 111    
( Solução ) Escreva uma expressão condicional lookbehind negativa que capture texto ownssomente se não for precedido por texto cle que capture texto oudssomente quando for precedido por texto cl. (Um exemplo um pouco artificial, mas o que você pode fazer...)
padrão:
string: Esses palhaços são donos de algumas nuvens . ousa.
correspondências:              ^^^^ ^^^^   
( Solução ) <h2>Etapa 20: Recursão e estudo adicional</h2> RegEx: 20 passos curtos para dominar expressões regulares.  Parte 4 - 6Na verdade, há muita coisa que pode ser espremida em uma introdução de 20 passos para qualquer tópico, e as expressões regulares não são exceção. Existem muitas implementações e padrões diferentes para expressões regulares que podem ser encontrados na Internet. Se você quiser saber mais, sugiro que dê uma olhada no maravilhoso site regularexpressions.info , é uma referência fantástica e com certeza aprendi muito sobre expressões regulares com lá. Eu recomendo fortemente, assim como regex101.com para testar e publicar suas criações. Nesta etapa final, darei a você um pouco mais de conhecimento sobre expressões regulares, ou seja, como escrever expressões recursivas. Recursões simples são bastante diretas, mas vamos pensar no que isso significa no contexto de uma expressão regular. A sintaxe para recursão simples em uma expressão regular é escrita assim: (?R)?. Mas, claro, esta sintaxe deve aparecer dentro da própria expressão. O que faremos é aninhar a expressão dentro dela mesma, um número arbitrário de vezes. Por exemplo:
padrão: (hey(?R)?oh) 
string:   heyoh heyyoh heyheyohoh hey oh heyhey hey heyheyohoh  
corresponde a: ^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^ 
grupo:    11111 1111111111 1111111111    
( Exemplo ) Como a expressão aninhada é opcional ( (?R)seguida ?), a correspondência mais simples é simplesmente ignorar completamente a recursão. Então, hey, e então ohcorresponde a ( heyoh). Para corresponder a qualquer expressão mais complexa do que esta, devemos encontrar a substring correspondente aninhada dentro de si mesma no ponto da expressão onde inserimos (?R)a sequência. Em outras palavras, poderíamos encontrar heyheyohoh ou heyheyheyohohoh e assim por diante. Uma das melhores coisas sobre essas expressões aninhadas é que, diferentemente das referências anteriores e dos grupos de captura nomeados, elas não restringem você ao texto exato com o qual você combinou anteriormente, caractere por caractere. Por exemplo:
padrão: ([Hh][Ee][Yy](?R)?oh) 
string:   heyoh heyyoh hEyHeYohoh ei oh heyhey hEyHeYHEyohohoh  
corresponde a: ^^^^^ ^^^^^^^^^^ ^^^^^ ^^^^^^^^^^ 
grupo:    11111 1111111111 111111111111111    
( Exemplo ) Você pode imaginar que o mecanismo de expressão regular literalmente copia e cola sua expressão regular em si mesmo um número arbitrário de vezes. Claro, isso significa que às vezes pode não fazer o que você esperava:
padrão: ((?:\(\*)[^*)]*(?R)?(?:\*\))) 
string: (* comentário (* aninhado *) não *)
correspondências:            ^^^^^^^^^^^^ 
grupo:               111111111111    
( Exemplo ) Você pode dizer por que esse regex capturou apenas o comentário aninhado e não o comentário externo? Uma coisa é certa: ao escrever expressões regulares complexas, sempre teste-as para ter certeza de que funcionam da maneira que você imagina. Este rali de alta velocidade pelas estradas de expressão regular chegou ao fim. Espero que você tenha gostado desta jornada. Pois bem, e por fim, deixarei aqui, como prometi no início, vários links úteis para um estudo mais aprofundado do material:
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION