Twórz wyrażenia regularne w locie, używając zmiennych łańcuchowych


138

Powiedzmy, że chcę, aby następujące elementy były ponownie używane:

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

Mógłbym zrobić coś takiego:

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

W przypadku literałów tekstowych jest to dość łatwe. Ale co, jeśli chcę trochę skomplikować z wyrażeniem regularnym? Na przykład powiedz, że chcę zastąpić wszystko, ale string_to_replace . Instynktownie spróbuję rozszerzyć powyższe, robiąc coś takiego:

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

To nie działa. Domyślam się, że uważa, że string_to_replacejest to literał łańcuchowy, a nie zmienna reprezentująca ciąg. Czy możliwe jest tworzenie wyrażeń regularnych JavaScript w locie za pomocą zmiennych łańcuchowych? Coś takiego byłoby świetne, gdyby w ogóle możliwe:

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

Odpowiedzi:


215

Tam new RegExp(string, flags)gdzie flagsglub i. Więc

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

ocenia do

zilla

31
I pomiń /separatory wyrażeń regularnych, używając również tego formularza.
cdhowie

111

W przypadku literałów tekstowych jest to dość łatwe.

Nie całkiem! Przykład zastępuje tylko pierwsze wystąpienie string_to_replace. Częściej chcesz zamienić wszystkie wystąpienia, w takim przypadku musisz przekonwertować ciąg na global ( /.../g) RegExp. Możesz to zrobić ze stringa używając new RegExpkonstruktora:

new RegExp(string_to_replace, 'g')

Problem z tym polega na tym, że wszelkie znaki specjalne wyrażenia regularnego w literale ciągu będą się zachowywać w swój specjalny sposób, zamiast być zwykłymi znakami. Aby to naprawić, musiałbyś uciec od odwrotnego ukośnika. Niestety, nie ma wbudowanej funkcji, która mogłaby to zrobić za Ciebie, więc oto jedna, której możesz użyć:

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

Zauważ również, że kiedy używasz RegExp w replace(), ciąg zastępujący ma teraz również znak specjalny $. To również musi zostać zmienione, jeśli chcesz mieć literał $w tekście zastępczym!

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

(Cztery $s, ponieważ sam w sobie jest ciągiem zastępczym - argh!)

Teraz możesz zaimplementować globalne zastępowanie ciągów za pomocą 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);
}

Co za ból. Na szczęście, jeśli wszystko, co chcesz zrobić, to zastąpienie prostego ciągu bez dodatkowych części wyrażenia regularnego, jest szybszy sposób:

s.split(string_to_replace).join(replacement)

...i to wszystko. To jest powszechnie rozumiany idiom.

powiedz, że chcę zastąpić wszystko oprócz string_to_replace

Co to znaczy, że chcesz zamienić wszystkie fragmenty tekstu, które nie biorą udziału w dopasowaniu do ciągu? Zamiana na z ^pewnością tego nie robi, ponieważ ^oznacza token początku łańcucha, a nie negację. ^jest tylko negacją w []grupach znaków. Istnieją również negatywne lookahead (?!...), ale są z tym problemy w JScript, więc generalnie należy tego unikać.

Możesz spróbować dopasować „wszystko do” do ciągu i użyć funkcji do odrzucenia pustego odcinka między pasującymi ciągami:

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

Tutaj znowu podział może być prostszy:

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);

10

Jak powiedzieli inni, użyj new RegExp(pattern, flags)do tego. Warto zauważyć, że do tego konstruktora będziesz przekazywał literały łańcuchowe, więc każdy lewy ukośnik będzie musiał zostać zmieniony. Jeśli na przykład chciałbyś, aby twoje wyrażenie regularne pasowało do ukośnika odwrotnego, musiałbyś powiedzieć new RegExp('\\\\'), podczas gdy literał wyrażenia regularnego musiałby tylko być /\\/. W zależności od tego, jak zamierzasz tego użyć, powinieneś uważać na przekazywanie danych wejściowych użytkownika do takiej funkcji bez odpowiedniego przetwarzania wstępnego (unikanie znaków specjalnych itp.). Bez tego użytkownicy mogą uzyskać bardzo nieoczekiwane wyniki.


3
Ta odpowiedź, choć nie jest najbardziej szczegółowa, wspomina o kluczowym szczególe, na którym utknąłem na godzinę: unikaj specjalnych sekwencji. Na przykład szukałem słowa zaczynającego się od określonego terminu, więc potrzebuję wyrażenia regularnego /\b[term]\B/, ale podczas konstruowania go muszę wywołać new RegExp("\\b"+ term + "\\B"). Mały, ale ważna różnica, i trudne do wykrycia, ponieważ używając go jako regex bezpośrednio wykonuje prace zgodnie z oczekiwaniami.
Byson


0

Myślę, że mam bardzo dobry przykład podświetlenia tekstu w ciągu (znajduje nie patrząc na rejestr, ale podświetlony za pomocą rejestru)

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/


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.