Créez des RegExps à la volée à l'aide de variables de chaîne

138

Disons que je voulais rendre les éléments suivants réutilisables:

function replace_foo(target, replacement) {
   return target.replace("string_to_replace",replacement);
}

Je pourrais faire quelque chose comme ça:

function replace_foo(target, string_to_replace, replacement) {
   return target.replace(string_to_replace,replacement);
}

Avec les chaînes littérales, c'est assez simple. Mais que faire si je veux devenir un peu plus délicat avec l'expression régulière? Par exemple, disons que je veux tout remplacer mais string_to_replace . Instinctivement, j'essaierais d'étendre ce qui précède en faisant quelque chose comme:

function replace_foo(target, string_to_replace, replacement) {
   return target.replace(/^string_to_replace/,replacement);
}

Cela ne semble pas fonctionner. Je suppose qu'il pense qu'il string_to_replaces'agit d'une chaîne littérale, plutôt qu'une variable représentant une chaîne. Est-il possible de créer des expressions régulières JavaScript à la volée en utilisant des variables de chaîne? Quelque chose comme ça serait génial si possible:

function replace_foo(target, string_to_replace, replacement) {
   var regex = "/^" + string_to_replace + "/";
   return target.replace(regex,replacement);
}
Buley
la source

Réponses:

215

Voilà new RegExp(string, flags)flagssont gou i. Alors

'GODzilla'.replace( new RegExp('god', 'i'), '' )

évalue à

zilla
meder omuraliev
la source
31
Et omettez les /délimiteurs de regex lorsque vous utilisez également ce formulaire.
cdhowie
111

Avec les chaînes littérales, c'est assez simple.

Pas vraiment! L'exemple ne remplace que la première occurrence de string_to_replace. Le plus souvent, vous souhaitez remplacer toutes les occurrences, auquel cas vous devez convertir la chaîne en un /.../gRegExp global ( ). Vous pouvez le faire à partir d'une chaîne en utilisant le new RegExpconstructeur:

new RegExp(string_to_replace, 'g')

Le problème avec ceci est que tous les caractères spéciaux de regex dans la chaîne littérale se comporteront de leur manière spéciale au lieu d'être des caractères normaux. Vous devrez leur échapper une barre oblique inverse pour résoudre ce problème. Malheureusement, il n'y a pas de fonction intégrée pour faire cela pour vous, alors en voici une que vous pouvez utiliser:

function escapeRegExp(s) {
    return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
}

Notez également que lorsque vous utilisez une expression rationnelle replace(), la chaîne de remplacement a maintenant un caractère spécial aussi $. Cela doit également être échappé si vous voulez avoir un littéral $dans votre texte de remplacement!

function escapeSubstitute(s) {
    return s.replace(/\$/g, '$$$$');
}

(Quatre $s parce que c'est en soi une chaîne de remplacement - argh!)

Vous pouvez maintenant implémenter le remplacement de chaîne global avec RegExp:

function replace_foo(target, string_to_replace, replacement) {
    var relit= escapeRegExp(string_to_replace);
    var sub= escapeSubstitute(replacement);
    var re= new RegExp(relit, 'g');
    return target.replace(re, sub);
}

Quelle douleur. Heureusement, si tout ce que vous voulez faire est un remplacement de chaîne droite sans parties supplémentaires de regex, il existe un moyen plus rapide:

s.split(string_to_replace).join(replacement)

...et c'est tout. C'est un idiome communément compris.

dis que je veux tout remplacer sauf string_to_replace

Qu'est-ce que cela signifie, vous souhaitez remplacer toutes les parties de texte ne participant pas à une correspondance par rapport à la chaîne? Un remplacement par ^n'est certainement pas cela, car cela ^signifie un jeton de début de chaîne, pas une négation. ^n'est qu'une négation dans les []groupes de caractères. Il y a aussi des anticipations de recherche négatives (?!...), mais il y a des problèmes avec cela dans JScript donc vous devriez généralement l'éviter.

Vous pouvez essayer de faire correspondre `` tout jusqu'à '' la chaîne et d'utiliser une fonction pour supprimer tout étirement vide entre les chaînes correspondantes:

var re= new RegExp('(.*)($|'+escapeRegExp(string_to_find)+')')
return target.replace(re, function(match) {
    return match[1]===''? match[2] : replacement+match[2];
});

Ici encore, une scission pourrait être plus simple:

var parts= target.split(string_to_match);
for (var i= parts.length; i-->0;)
    if (parts[i]!=='')
        parts[i]= replacement;
return parts.join(string_to_match);
bobince
la source
10

Comme les autres l'ont dit, utilisez new RegExp(pattern, flags)pour le faire. Il est à noter que vous passerez des chaînes littérales dans ce constructeur, donc chaque barre oblique inverse devra être échappée. Si, par exemple, vous vouliez que votre regex corresponde à une barre oblique inverse, vous auriez besoin de le dire new RegExp('\\\\'), alors que le littéral regex n'aurait besoin que de l'être /\\/. En fonction de la façon dont vous comptez utiliser cela, vous devez vous méfier de la transmission des entrées utilisateur à une telle fonction sans un prétraitement adéquat (échappement de caractères spéciaux, etc.) Sans cela, vos utilisateurs peuvent obtenir des résultats très inattendus.

Kent
la source
3
Cette réponse, bien que n'étant pas la plus détaillée, mentionne un détail crucial sur lequel je suis resté coincé pendant une heure: échapper à toute séquence spéciale. Par exemple, je cherchais un mot commençant par un certain terme, donc l'expression régulière dont j'avais besoin est /\b[term]\B/, mais lors de la construction, je dois appeler new RegExp("\\b"+ term + "\\B"). Petite mais importante différence, et difficile à repérer depuis l' utiliser comme regex directement fait le travail comme prévu.
Byson le
0

Je pense que j'ai un très bon exemple pour mettre en évidence le texte dans la chaîne (il ne trouve pas de recherche au registre mais mis en évidence à l'aide du registre)

function getHighlightedText(basicString, filterString) {

    if ((basicString === "") || (basicString === null) || (filterString === "") || (filterString === null)) return basicString;

    return basicString.replace(new RegExp(filterString.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\\\$&'), 'gi'),
        function(match)
            {return "<mark>"+match+"</mark>"});

}

http://jsfiddle.net/cdbzL/1258/

Zhurov Konstantin
la source
0

Une solution très simple à cela est la suivante:

function replace(target, string_to_replace, replacement) {
  return target.split(string_to_replace).join(replacement);
}

Pas besoin de regex du tout

Il semble également être le plus rapide sur les navigateurs modernes https://jsperf.com/replace-vs-split-join-vs-replaceall

Jack Allan
la source