Comment remplacer uniquement les groupes capturés?

196

J'ai du code HTML avant et après la chaîne:

name="some_text_0_some_text"

Je voudrais remplacer le 0par quelque chose comme:!NEW_ID!

Alors j'ai fait une simple regex:

.*name="\w+(\d+)\w+".*

Mais je ne vois pas comment remplacer exclusivement le bloc capturé.

Existe-t-il un moyen de remplacer un résultat capturé comme ($ 1) par une autre chaîne?

Le résultat serait:

name="some_text_!NEW_ID!_some_text"
Nicolas Guillaume
la source

Réponses:

359

Une solution consiste à ajouter des captures pour le texte précédent et suivant:

str.replace(/(.*name="\w+)(\d+)(\w+".*)/, "$1!NEW_ID!$3")
Matthew Flaschen
la source
76
Salutations du futur! Votre solution est vraiment soignée. Pouvez-vous expliquer votre réponse?
Polyducks
21
Les parenthèses sont utilisées pour créer des «groupes», qui se voient ensuite attribuer un index de base 1, accessible dans un remplacement par a $, de sorte que le premier mot (\w+)est dans un groupe, et devient $1, la partie centrale (\d+)est le deuxième groupe, (mais obtient ignoré dans le remplacement), et le troisième groupe est $3. Ainsi, lorsque vous donnez la chaîne de remplacement de "$1!new_ID!$3", les $ 1 et $ 3 sont automatiquement remplacés par le premier groupe et le troisième groupe, ce qui permet de remplacer le deuxième groupe par la nouvelle chaîne, en conservant le texte qui l'entoure.
mix3d
4
Cela étant dit, bien que je comprenne COMMENT cela fonctionne, j'espérais une solution plus élégante>. <Néanmoins, je peux avancer avec mon code maintenant!
mix3d
9
1) Vous n'avez même pas besoin de capturer \ d + 2) Pourquoi dites-vous que ce n'est pas élégant? La capture est destinée à garder des choses, pas à les jeter. Ce que vous voulez garder, c'est ce qui est AROUND \ d +, donc il est vraiment logique (et assez élégant) de capturer ces parties environnantes.
Sir4ur0n
3
Belle solution. Et si nous voulons remplacer les groupes de capture en utilisant le groupe de capture comme base de la transformation? Existe-t-il une solution tout aussi élégante pour faire cela? Actuellement, je stocke les groupes capturés dans une liste, je les boucle et je remplace le groupe de capture par la valeur transformée à chaque itération
sookie
15

Maintenant que Javascript a regardé en arrière (à partir de ES2018 ), sur les environnements plus récents, vous pouvez éviter complètement les groupes dans des situations comme celles-ci. Au lieu de cela, regardez en arrière pour ce qui précède le groupe que vous capturiez, et cherchez à l'avance pour venir après, et remplacez par juste !NEW_ID! :

const str = 'name="some_text_0_some_text"';
console.log(
  str.replace(/(?<=name="\w+)\d+(?=\w+")/, '!NEW_ID!')
);

Avec cette méthode, la correspondance complète est uniquement la partie qui doit être remplacée.

  • (?<=name="\w+)- Rechercher en arrière name", suivi des caractères de mots (heureusement, les regards en arrière n'ont pas à être de largeur fixe en Javascript!)
  • \d+ - Faire correspondre un ou plusieurs chiffres - la seule partie du modèle qui ne fait pas partie d'une recherche, la seule partie de la chaîne qui sera dans la correspondance résultante
  • (?=\w+")- Lookahead pour les caractères de mots suivis de " `

Gardez à l'esprit que regarder en arrière est assez nouveau. Il fonctionne dans les versions modernes de V8 (y compris Chrome, Opera et Node), mais pas dans la plupart des autres environnements , du moins pas encore. Ainsi, bien que vous puissiez utiliser de manière fiable lookbehind dans Node et dans votre propre navigateur (s'il fonctionne sur une version moderne de V8), il n'est pas encore suffisamment pris en charge par des clients aléatoires (comme sur un site Web public).

CertainPerformance
la source
Je viens de faire un test de chronométrage rapide, et c'est assez impressionnant à quel point l'entrée est importante: jsfiddle.net/60neyop5
Kaiido
Mais si, par exemple, je veux extraire le nombre, le multiple et le «remettre», je vais devoir grouper aussi \d+, non?
Mosh Feu
@MoshFeu Utilisez une fonction de remplacement et utilisez toute la correspondance, les chiffres: remplacez le deuxième paramètre par match => match * 2. Les chiffres sont toujours le match complet, donc pas besoin de groupes
CertainPerformance
Je t'ai eu. Merci!
Mosh Feu
2

Une petite amélioration de la réponse de Matthew pourrait être une anticipation au lieu du dernier groupe de capture:

.replace(/(\w+)(\d+)(?=\w+)/, "$1!NEW_ID!");

Ou vous pouvez diviser sur la décimale et rejoindre votre nouvel identifiant comme ceci:

.split(/\d+/).join("!NEW_ID!");

Exemple / Benchmark ici: https://codepen.io/jogai/full/oyNXBX

Jogai
la source
1

Avec deux groupes de capture aurait été également possible; J'aurais également inclus deux tirets, comme limites supplémentaires gauche et droite, avant et après les chiffres, et l'expression modifiée aurait ressemblé à:

(.*name=".+_)\d+(_[^"]+".*)

const regex = /(.*name=".+_)\d+(_[^"]+".*)/g;
const str = `some_data_before name="some_text_0_some_text" and then some_data after`;
const subst = `$1!NEW_ID!$2`;
const result = str.replace(regex, subst);
console.log(result);


Si vous souhaitez explorer / simplifier / modifier l'expression, cela a été expliqué dans le panneau supérieur droit de regex101.com . Si vous le souhaitez, vous pouvez également regarder dans ce lien , comment il correspondrait à certains exemples d'entrées.


Circuit RegEx

jex.im visualise les expressions régulières:

entrez la description de l'image ici

Emma
la source
0

Une option plus simple consiste simplement à capturer les chiffres et à les remplacer.

const name = 'preceding_text_0_following_text';
const matcher = /(\d+)/;

// Replace with whatever you would like
const newName = name.replace(matcher, 'NEW_STUFF');
console.log("Full replace", newName);

// Perform work on the match and replace using a function
// In this case increment it using an arrow function
const incrementedName = name.replace(matcher, (match) => ++match);
console.log("Increment", incrementedName);

Ressources

CTS_AE
la source