Até onde eu sei, não existe o nome de grupos de captura em JavaScript. Qual é a maneira alternativa de obter funcionalidade semelhante?
Até onde eu sei, não existe o nome de grupos de captura em JavaScript. Qual é a maneira alternativa de obter funcionalidade semelhante?
Respostas:
O ECMAScript 2018 introduz grupos de captura nomeados em expressões regulares do JavaScript.
Exemplo:
const auth = 'Bearer AUTHORIZATION_TOKEN'
const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
console.log(token) // "Prints AUTHORIZATION_TOKEN"
Se você precisar oferecer suporte a navegadores antigos, poderá fazer tudo com grupos de captura normais (numerados) que você pode fazer com grupos de captura nomeados, basta acompanhar os números - o que pode ser complicado se a ordem do grupo de captura em seu mudanças de regex.
Existem apenas duas vantagens "estruturais" dos grupos de captura nomeados em que posso pensar:
Em alguns tipos 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 em que isso importa ). Mas a maioria dos tipos de expressões regulares não suporta essa funcionalidade.
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 obter um problema. Digamos que você deseja adicionar um zero a um dígito e, portanto, deseja substituir (\d)
por $10
. Em JavaScript, isso funcionará (contanto que você tenha menos de 10 grupos de capturas em sua regex), mas Perl pensará que você está procurando por um número de referência anterior em 10
vez de um número 1
, seguido por a 0
. No Perl, você pode usar ${1}0
neste caso.
Fora isso, os grupos de captura nomeados são apenas "açúcar sintático". Ajuda a usar grupos de captura somente quando você realmente precisa deles e a usar grupos que não capturam (?:...)
em todas as outras circunstâncias.
O maior problema (na minha opinião) do JavaScript é que ele não suporta expressões verbais que facilitariam muito a criação de expressões regulares complexas e legíveis.
A biblioteca XRegExp de Steve Levithan resolve esses problemas.
Você pode usar o XRegExp , uma implementação aumentada, extensível e cruzada de expressões regulares, incluindo suporte para sintaxe, sinalizadores e métodos adicionais:
s
:, para fazer o ponto corresponder a todos os caracteres (também conhecido como modo dotall ou linha única) e x
, para espaçamento livre e comentários (conhecido como modo estendido).Outra solução possível: crie um objeto contendo os nomes e índices do grupo.
var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };
Em seguida, use as teclas de objeto para fazer referência aos grupos:
var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];
Isso melhora a legibilidade / qualidade do código usando os resultados da regex, mas não a legibilidade da própria regex.
No ES6, você pode usar a destruição de matriz para capturar seus grupos:
let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];
// count === '27'
// unit === 'months'
Aviso prévio:
let
pula o primeiro valor da matriz resultante, que é toda a cadeia correspondente|| []
after .exec()
evitará um erro de desestruturação quando não houver correspondências (porque .exec()
retornará null
)String.prototype.match
retorna uma matriz com: toda a cadeia correspondente na posição 0, depois quaisquer grupos depois disso. A primeira vírgula diz "pular o elemento na posição 0"
RegExp.prototype.exec
mais String.prototype.match
em lugares onde a string pode ser null
ou undefined
.
Atualização: finalmente transformou-se em JavaScript (ECMAScript 2018)!
Grupos de captura nomeados podem entrar no JavaScript muito em breve.
A proposta já está no estágio 3.
Um grupo de captura pode receber um nome entre colchetes angulares usando a (?<name>...)
sintaxe, para qualquer nome de identificador. A expressão regular de uma data pode ser escrita como /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
. Cada nome deve ser exclusivo e seguir a gramática para ECMAScript IdentifierName .
Grupos nomeados podem ser acessados a partir de propriedades de uma propriedade de grupos do resultado da expressão regular. Também são criadas referências numeradas para os grupos, assim como para grupos sem nome. Por exemplo:
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\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';
let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
A nomeação de grupos capturados fornece uma coisa: menos confusão com expressões regulares complexas.
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 aos seus grupos capturados.
Os comentários também podem ajudar a mostrar aos outros que leem seu código, o que você fez.
Quanto ao resto, devo concordar com a resposta de Tims.
Existe uma biblioteca node.js chamada named-regexp que você pode usar em seus projetos node.js. (ativada no navegador empacotando a biblioteca com o browserify ou outros scripts de empacotamento). No entanto, a biblioteca não pode ser usada com expressões regulares que contêm grupos de captura sem nome.
Se você contar os chavetas 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 poderá misturar e combinar livremente. Você apenas precisa remover os nomes dos grupos antes de usar a regex. Eu escrevi três funções que demonstram isso. Veja esta lista: https://gist.github.com/gbirke/2cc2370135b665eee3ef
Como Tim Pietzcker disse, o ECMAScript 2018 introduz grupos de captura nomeados nas expressões regulares do JavaScript. Mas o que não encontrei nas respostas acima foi como usar o grupo capturado nomeado no próprio regex.
você pode usar grupo capturado nomeado com a seguinte sintaxe: \k<name>
. por exemplo
var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/
e, como Forivin disse, você pode usar o grupo capturado no resultado do objeto da seguinte maneira:
let result = regexObj.exec('2019-28-06 year is 2019');
// result.groups.year === '2019';
// result.groups.month === '06';
// result.groups.day === '28';
var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/mgi;
function check(){
var inp = document.getElementById("tinput").value;
let result = regexObj.exec(inp);
document.getElementById("year").innerHTML = result.groups.year;
document.getElementById("month").innerHTML = result.groups.month;
document.getElementById("day").innerHTML = result.groups.day;
}
td, th{
border: solid 2px #ccc;
}
<input id="tinput" type="text" value="2019-28-06 year is 2019"/>
<br/>
<br/>
<span>Pattern: "(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>";
<br/>
<br/>
<button onclick="check()">Check!</button>
<br/>
<br/>
<table>
<thead>
<tr>
<th>
<span>Year</span>
</th>
<th>
<span>Month</span>
</th>
<th>
<span>Day</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span id="year"></span>
</td>
<td>
<span id="month"></span>
</td>
<td>
<span id="day"></span>
</td>
</tr>
</tbody>
</table>
Embora você não possa fazer isso com JavaScript vanilla, talvez você possa usar alguma Array.prototype
função como Array.prototype.reduce
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 (f.e. /.+/)
// @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 ([a-z]+), I am ([a-z]+)/i,
["firstPersonName", "secondPersonName"]
);
alert(JSON.stringify(namedMatches));
var assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
RegExp
objeto adicionando uma função ao seu protótipo.
Não possui o ECMAScript 2018?
Meu objetivo era fazê-lo funcionar o mais semelhante possível ao que estamos acostumados com grupos nomeados. Enquanto no ECMAScript 2018 você pode colocar ?<groupname>
dentro do grupo para indicar um grupo nomeado, na minha solução para javascript antigo, você pode colocar (?!=<groupname>)
dentro do grupo para fazer a mesma coisa. Portanto, é um conjunto extra de parênteses e um extra!=
. Bem perto!
Eu envolvi tudo isso em uma função de protótipo de string
Recursos
Instruções
(?!={groupname})
dentro de cada grupo que você deseja nomear()
colocando ?:
no início desse grupo. Estes não serão nomeados.arrays.js
// @@pattern - includes injections of (?!={groupname}) for each group
// @@returns - an object with a property for each group having the group's match as the value
String.prototype.matchWithGroups = function (pattern) {
var matches = this.match(pattern);
return pattern
// get the pattern as a string
.toString()
// suss out the groups
.match(/<(.+?)>/g)
// remove the braces
.map(function(group) {
return group.match(/<(.+)>/)[1];
})
// create an object with a property for each group having the group's match as the value
.reduce(function(acc, curr, index, arr) {
acc[curr] = matches[index + 1];
return acc;
}, {});
};
uso
function testRegGroups() {
var s = '123 Main St';
var pattern = /((?!=<house number>)\d+)\s((?!=<street name>)\w+)\s((?!=<street type>)\w+)/;
var o = s.matchWithGroups(pattern); // {'house number':"123", 'street name':"Main", 'street type':"St"}
var j = JSON.stringify(o);
var housenum = o['house number']; // 123
}
resultado de o
{
"house number": "123",
"street name": "Main",
"street type": "St"
}