Nomeados grupos de captura em JavaScript regex?

Tanto quanto eu sei, não há tal coisa como grupos de captura nomeados em JavaScript. Qual é a maneira alternativa de obter funcionalidade semelhante?

O ECMAScript 2018 introduz grupos de captura nomeados em regexes de JavaScript.

Se você precisar dar suporte a navegadores mais antigos, você pode fazer tudo com grupos de captura normais (numerados) que você pode fazer com grupos de captura nomeados, você só precisa acompanhar os números – o que pode ser complicado se a ordem de captura do grupo regex muda.

Existem apenas duas vantagens “estruturais” de grupos de captura nomeados em que posso pensar:

  1. Em alguns sabores de regex (.NET e JGSoft, até onde eu sei), você pode usar o mesmo nome para diferentes grupos em seu regex ( veja aqui um exemplo onde isso é importante ). Mas a maioria dos sabores de regex não suporta essa funcionalidade de qualquer maneira.

  2. Se você precisar se referir a grupos de captura numerados em uma situação em que eles estão cercados por dígitos, você pode ter um problema. Digamos que você queira adicionar um zero a um dígito e, portanto, deseja replace (\d) por $10 . Em JavaScript, isso funcionará (contanto que você tenha menos de 10 grupos de captura no seu regex), mas o Perl achará que você está procurando a referência de referência número 10 vez do número 1 , seguido por um 0 . Em Perl, você pode usar ${1}0 neste caso.

Além disso, os grupos capturados são apenas “açúcar sintático”. Isso ajuda a usar grupos de captura somente quando você realmente precisa deles e usar grupos que não capturam (?:...) em todas as outras circunstâncias.

O maior problema (na minha opinião) com o JavaScript é que ele não suporta expressões regulares detalhadas, o que facilitaria muito a criação de expressões regulares complexas e legíveis.

A biblioteca XRegExp de Steve Levithan resolve esses problemas.

Você pode usar XRegExp , uma implementação de expressões regulares ampliada, extensível e em vários navegadores, incluindo suporte a syntax, sinalizadores e methods adicionais:

  • Adiciona nova syntax de texto de regex e substituição, incluindo suporte abrangente para captura nomeada .
  • Adiciona dois novos sinalizadores de regex: s , para fazer o ponto corresponder a todos os caracteres (também conhecido como modo pontal ou singleline), e x , para espaços livres e comentários (também conhecido como modo estendido).
  • Fornece um conjunto de funções e methods que facilitam o processamento complexo de expressões regulares.
  • Automagicamente corrige as inconsistências entre navegadores mais comumente encontradas no comportamento e na syntax do regex.
  • Permite criar e usar facilmente plugins que adicionam nova syntax e sinalizadores ao idioma de expressão regular do XRegExp.

Outra solução possível: crie um object contendo os nomes e índices do grupo.

 var regex = new RegExp("(.*) (.*)"); var regexGroups = { FirstName: 1, LastName: 2 }; 

Em seguida, use as chaves de object para referenciar os grupos:

 var m = regex.exec("John Smith"); var f = m[regexGroups.FirstName]; 

Isso melhora a legibilidade / qualidade do código usando os resultados do regex, mas não a legibilidade do próprio regex.

No ES6, você pode usar a desestruturação de matriz para capturar seus grupos:

 let text = '27 months'; let regex = /(\d+)\s*(days?|months?|years?)/; let [, count, unit] = text.match(regex) || []; // count === '27' // unit === 'months' 

Aviso prévio:

  • a primeira vírgula no último let ignora o primeiro valor da matriz resultante, que é toda a string correspondente
  • o || [] || [] após .match() irá prevenir um erro de desestruturação quando não houver correspondências (porque .match() retornará null )

Nomear grupos capturados fornece uma coisa: menos confusão com expressões regulares complexas.

Isso realmente depende do seu caso de uso, mas talvez a impressão bonita do seu regex possa ajudar.

Ou você pode tentar definir constantes para se referir a seus grupos capturados.

Os comentários também podem ajudar a mostrar aos outros que lêem o seu código o que você fez.

Para o resto, devo concordar com a resposta de Tim.

Existe uma biblioteca node.js chamada named-regexp que você pode usar em seus projetos node.js (no navegador, empacotando a biblioteca com browserify ou outros scripts de empacotamento). No entanto, a biblioteca não pode ser usada com expressões regulares que contenham grupos de captura não nomeados.

Se você contar as chaves de captura de abertura em sua expressão regular, poderá criar um mapeamento entre os grupos de captura nomeados e os grupos de captura numerados em sua regex e pode misturar e combinar livremente. Você só precisa remover os nomes dos grupos antes de usar o regex. Eu escrevi três funções que demonstram isso. Veja esta essência: https://gist.github.com/gbirke/2cc2370135b665eee3ef

Grupos de captura nomeados podem entrar em JavaScript muito em breve.
A proposta para isso já está na fase 3.

Um grupo de captura pode receber um nome usando a syntax (? …), para qualquer nome de identificador. A expressão regular para uma data pode então ser escrita como / (? \ D {4}) – (? \ D {2}) – (? \ D {2}) / u. Cada nome deve ser único e seguir a gramática para o ECMAScript IdentifierName.

Grupos nomeados podem ser acessados ​​a partir de propriedades de uma propriedade de grupos do resultado da expressão regular. Referências numeradas aos grupos também são criadas, assim como para grupos não nomeados. Por exemplo:

 let re = /(?\d{4})-(?\d{2})-(?\d{2})/u; let result = re.exec('2015-01-02'); // result.groups.year === '2015'; // result.groups.month === '01'; // result.groups.day === '02'; // result[0] === '2015-01-02'; // result[1] === '2015'; // result[2] === '01'; // result[3] === '02'; 

Embora você não possa fazer isso com JavaScript vanilla, talvez você possa usar alguma function Array.prototype.reduce como Array.prototype.reduce para transformar correspondências indexadas em nomeadas usando alguma mágica .

Obviamente, a seguinte solução precisará que as correspondências ocorram em ordem:

 // @text Contains the text to match // @regex A regular expression object (fe /.+/) // @matchNames An array of literal strings where each item // is the name of each group function namedRegexMatch(text, regex, matchNames) { var matches = regex.exec(text); return matches.reduce(function(result, match, index) { if (index > 0) // This substraction is required because we count // match indexes from 1, because 0 is the entire matched string result[matchNames[index - 1]] = match; return result; }, {}); } var myString = "Hello Alex, I am John"; var namedMatches = namedRegexMatch( myString, /Hello ([az]+), I am ([az]+)/i, ["firstPersonName", "secondPersonName"] ); alert(JSON.stringify(namedMatches));