Potrzebuję wyrażenia regularnego, aby zaznaczyć cały tekst między dwoma nawiasami zewnętrznymi.
Przykład: some text(text here(possible text)text(possible text(more text)))end text
Wynik: (text here(possible text)text(possible text(more text)))
Potrzebuję wyrażenia regularnego, aby zaznaczyć cały tekst między dwoma nawiasami zewnętrznymi.
Przykład: some text(text here(possible text)text(possible text(more text)))end text
Wynik: (text here(possible text)text(possible text(more text)))
Odpowiedzi:
Wyrażenia regularne są niewłaściwym narzędziem dla zadania, ponieważ mamy do czynienia z zagnieżdżonymi strukturami, tj. Rekurencją.
Istnieje jednak prosty algorytm do tego, który opisałem w odpowiedzi na poprzednie pytanie .
Chcę dodać tę odpowiedź w celu szybkiego odwołania. Aktualizuj.
.NET Regex przy użyciu grup bilansujących .
\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)
Gdzie c
jest używany jako licznik głębokości.
PCRE przy użyciu wzorca rekurencyjnego .
\((?:[^)(]+|(?R))*+\)
Demo w regex101 ; Lub bez zmian:
\((?:[^)(]*(?R)?)*+\)
Demo w regex101 ; Lub rozwinięty dla wydajności:
\([^)(]*+(?:(?R)[^)(]*)*+\)
Demo w regex101 ; Wzór jest wklejony, w (?R)
którym reprezentuje (?0)
.
Perl, PHP, Notepad ++, R : perl = TRUE , pakiet Python : Regex z (?V1)
zachowaniem Perla.
Ruby używa wywołań podwyrażenia .
Z Ruby 2.0 \g<0>
można użyć do wywołania pełnego wzorca.
\((?>[^)(]+|\g<0>)*\)
Demo w Rubular ; Ruby 1.9 obsługuje tylko przechwytywanie rekurencji grupowej :
(\((?>[^)(]+|\g<1>)*\))
Demo at Rubular ( grupowanie atomowe od Ruby 1.9.3)
JavaScript API :: XRegExp.matchRecursive
XRegExp.matchRecursive(str, '\\(', '\\)', 'g');
Smaki JS, Java i inne wyrażenia regularne bez rekurencji do 2 poziomów zagnieżdżenia:
\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)
Demo w regex101 . Głębsze zagnieżdżenie należy dodać do wzoru.
Aby szybciej zawieść przy niezrównoważonym nawiasie, upuść +
kwantyfikator.
Java : Ciekawy pomysł z wykorzystaniem referencji od @jaytea .
(?>[^)(]+|(?R))*+
jest takie samo jak pisanie (?:[^)(]+|(?R))*+
. To samo dla następnego wzoru. W przypadku wersji rozwiniętej możesz tutaj umieścić kwantyfikator dzierżawczy: [^)(]*+
aby zapobiec cofaniu się (w przypadku braku klamry zamykającej).
(...(..)..(..)..(..)..(..)..)
) w ciągu tematu), możesz użyć prostej, nieprzechwycącej grupy i zamknąć wszystko w grupie atomowej: (?>(?:[^)(]+|\g<1>)*)
( zachowuje się dokładnie jak kwantyfikator dzierżawczy). W Ruby 2.x dostępny jest kwantyfikator dzierżawczy.
Możesz użyć rekursji wyrażenia regularnego :
\(([^()]|(?R))*\)
Unrecognized grouping construct
.
[^\(]*(\(.*\))[^\)]*
[^\(]*
dopasowuje wszystko, co nie jest nawiasem otwierającym na początku łańcucha, (\(.*\))
przechwytuje wymagany podciąg zawarty w nawiasach i [^\)]*
dopasowuje wszystko, co nie jest nawiasem zamykającym na końcu łańcucha. Zauważ, że to wyrażenie nie próbuje dopasować nawiasów; bardziej odpowiedni byłby prosty parser (patrz odpowiedź Dehmanna ).
(?<=\().*(?=\))
Jeśli chcesz zaznaczyć tekst między dwoma pasującymi nawiasami, nie masz szczęścia z wyrażeniami regularnymi. To niemożliwe (*) .
Ten wyrażenie regularne zwraca tylko tekst między pierwszym otwierającym a ostatnim nawiasem zamykającym w ciągu.
(*) Chyba że silnik regex ma takie funkcje, jak grupy równoważące lub rekurencja . Liczba silników obsługujących takie funkcje powoli rośnie, ale wciąż nie są one powszechnie dostępne.
Ta odpowiedź wyjaśnia teoretyczne ograniczenie, dlaczego wyrażenia regularne nie są odpowiednim narzędziem do tego zadania.
Wyrażenia regularne nie mogą tego zrobić.
Wyrażenia regularne oparte są na modelu obliczeniowym znanym jako Finite State Automata (FSA)
. Jak wskazuje nazwa, a FSA
może zapamiętać tylko bieżący stan, nie ma informacji o poprzednich stanach.
Na powyższym schemacie S1 i S2 są dwoma stanami, w których S1 jest krokiem początkowym i końcowym. Więc jeśli spróbujemy z ciągiem 0110
, przejście wygląda następująco:
0 1 1 0
-> S1 -> S2 -> S2 -> S2 ->S1
W powyższych krokach, gdy jesteśmy na drugim S2
, czyli po parsowania 01
z 0110
FSA nie ma informacji o poprzednim 0
w 01
jak może zapamiętać tylko aktualny stan i wejście kolejny symbol.
W powyższym problemie musimy znać liczbę nawiasów otwierających; oznacza to, że należy go przechowywać w jakimś miejscu. Ale ponieważ FSAs
nie można tego zrobić, nie można napisać wyrażenia regularnego.
Jednak do wykonania tego zadania można napisać algorytm. Algorytmy ogólnie podlegają Pushdown Automata (PDA)
. PDA
jest o jeden poziom powyżej FSA
. PDA ma dodatkowy stos do przechowywania dodatkowych informacji. PDA mogą być użyte do rozwiązania powyższego problemu, ponieważ możemy ' push
' nawias otwierający na stosie i ' pop
' je, gdy napotkamy nawias zamykający. Jeśli na końcu stos jest pusty, wówczas otwierające i zamykające pasują nawiasy. W przeciwnym razie nie.
W rzeczywistości jest to możliwe przy użyciu wyrażeń regularnych .NET, ale nie jest to trywialne, więc przeczytaj uważnie.
Można przeczytać ciekawy artykuł tutaj . Konieczne może być również przeczytanie wyrażeń regularnych .NET. Możesz zacząć czytać tutaj .
<>
Zastosowano nawiasy kątowe, ponieważ nie wymagają one ucieczki.
Wyrażenie regularne wygląda następująco:
<
[^<>]*
(
(
(?<Open><)
[^<>]*
)+
(
(?<Close-Open>>)
[^<>]*
)+
)*
(?(Open)(?!))
>
To jest ostateczne wyrażenie regularne:
\(
(?<arguments>
(
([^\(\)']*) |
(\([^\(\)']*\)) |
'(.*?)'
)*
)
\)
Przykład:
input: ( arg1, arg2, arg3, (arg4), '(pip' )
output: arg1, arg2, arg3, (arg4), '(pip'
zwróć uwagę, że '(pip'
jest poprawnie zarządzany jako ciąg. (wypróbowano w regulatorze: http://sourceforge.net/projects/regulator/ )
Napisałem małą bibliotekę JavaScript o nazwie zrównoważony, aby pomóc w tym zadaniu. Możesz to zrobić, robiąc to
balanced.matches({
source: source,
open: '(',
close: ')'
});
Możesz nawet dokonać wymiany:
balanced.replacements({
source: source,
open: '(',
close: ')',
replace: function (source, head, tail) {
return head + source + tail;
}
});
Oto bardziej złożony i interaktywny przykład JSFiddle .
Dodając do odpowiedzi bobble bubble , istnieją inne smaki wyrażeń regularnych, w których obsługiwane są konstrukcje rekurencyjne.
Lua
Użyj %b()
( %b{}
/ %b[]
dla nawiasów klamrowych / nawiasów kwadratowych):
for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end
(patrz demo )Perl6 :
Nie nakładające się wielokrotne zrównoważone nawiasy pasują do:
my regex paren_any { '(' ~ ')' [ <-[()]>+ || <&paren_any> ]* }
say "Extract (a(b)c) and ((d)f(g))" ~~ m:g/<&paren_any>/;
# => (「(a(b)c)」 「((d)f(g))」)
Nałożenie wielu zrównoważonych nawiasów dopasowanych:
say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)
Zobacz demo .
re
Rozwiązanie non-regex w języku Python
Zobacz odpowiedź poke na Jak uzyskać wyrażenie między zrównoważonymi nawiasami .
Dostosowywane przez firmę Java rozwiązanie niebędące wyrażeniem regularnym
Oto konfigurowalne rozwiązanie, które pozwala na użycie dosłownych separatorów w Javie:
public static List<String> getBalancedSubstrings(String s, Character markStart,
Character markEnd, Boolean includeMarkers)
{
List<String> subTreeList = new ArrayList<String>();
int level = 0;
int lastOpenDelimiter = -1;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == markStart) {
level++;
if (level == 1) {
lastOpenDelimiter = (includeMarkers ? i : i + 1);
}
}
else if (c == markEnd) {
if (level == 1) {
subTreeList.add(s.substring(lastOpenDelimiter, (includeMarkers ? i + 1 : i)));
}
if (level > 0) level--;
}
}
return subTreeList;
}
}
Przykładowe użycie:
String s = "some text(text here(possible text)text(possible text(more text)))end text";
List<String> balanced = getBalancedSubstrings(s, '(', ')', true);
System.out.println("Balanced substrings:\n" + balanced);
// => [(text here(possible text)text(possible text(more text)))]
Wyrażenie regularne używające Ruby (wersja 1.9.3 lub nowsza):
/(?<match>\((?:\g<match>|[^()]++)*\))/
Potrzebujesz pierwszego i ostatniego nawiasu. Użyj czegoś takiego:
str.indexOf ('('); - da ci pierwsze wystąpienie
str.lastIndexOf (')'); - ostatni
Więc potrzebujesz ciągu między,
String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');
"""
Here is a simple python program showing how to use regular
expressions to write a paren-matching recursive parser.
This parser recognises items enclosed by parens, brackets,
braces and <> symbols, but is adaptable to any set of
open/close patterns. This is where the re package greatly
assists in parsing.
"""
import re
# The pattern below recognises a sequence consisting of:
# 1. Any characters not in the set of open/close strings.
# 2. One of the open/close strings.
# 3. The remainder of the string.
#
# There is no reason the opening pattern can't be the
# same as the closing pattern, so quoted strings can
# be included. However quotes are not ignored inside
# quotes. More logic is needed for that....
pat = re.compile("""
( .*? )
( \( | \) | \[ | \] | \{ | \} | \< | \> |
\' | \" | BEGIN | END | $ )
( .* )
""", re.X)
# The keys to the dictionary below are the opening strings,
# and the values are the corresponding closing strings.
# For example "(" is an opening string and ")" is its
# closing string.
matching = { "(" : ")",
"[" : "]",
"{" : "}",
"<" : ">",
'"' : '"',
"'" : "'",
"BEGIN" : "END" }
# The procedure below matches string s and returns a
# recursive list matching the nesting of the open/close
# patterns in s.
def matchnested(s, term=""):
lst = []
while True:
m = pat.match(s)
if m.group(1) != "":
lst.append(m.group(1))
if m.group(2) == term:
return lst, m.group(3)
if m.group(2) in matching:
item, s = matchnested(m.group(3), matching[m.group(2)])
lst.append(m.group(2))
lst.append(item)
lst.append(matching[m.group(2)])
else:
raise ValueError("After <<%s %s>> expected %s not %s" %
(lst, s, term, m.group(2)))
# Unit test.
if __name__ == "__main__":
for s in ("simple string",
""" "double quote" """,
""" 'single quote' """,
"one'two'three'four'five'six'seven",
"one(two(three(four)five)six)seven",
"one(two(three)four)five(six(seven)eight)nine",
"one(two)three[four]five{six}seven<eight>nine",
"one(two[three{four<five>six}seven]eight)nine",
"oneBEGINtwo(threeBEGINfourENDfive)sixENDseven",
"ERROR testing ((( mismatched ))] parens"):
print "\ninput", s
try:
lst, s = matchnested(s)
print "output", lst
except ValueError as e:
print str(e)
print "done"
Odpowiedź zależy od tego, czy należy dopasować pasujące zestawy nawiasów, czy tylko od pierwszego otwarcia do ostatniego zamknięcia w tekście wejściowym.
Jeśli chcesz dopasować pasujące nawiasy klamrowe, potrzebujesz czegoś więcej niż wyrażeń regularnych. - patrz @dehmann
Jeśli to tylko pierwsze otwarcie do ostatniego zamknięcia, zobacz @Zach
Zdecyduj, co chcesz zrobić:
abc ( 123 ( foobar ) def ) xyz ) ghij
W takim przypadku musisz zdecydować, jaki kod będzie pasował.
ponieważ js regex nie obsługuje dopasowywania rekurencyjnego, nie mogę sprawić, by zrównoważone nawiasy były dopasowane.
więc jest to prosty javascript dla wersji pętli, który przekształca ciąg „method (arg)” w tablicę
push(number) map(test(a(a()))) bass(wow, abc)
$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)
const parser = str => {
let ops = []
let method, arg
let isMethod = true
let open = []
for (const char of str) {
// skip whitespace
if (char === ' ') continue
// append method or arg string
if (char !== '(' && char !== ')') {
if (isMethod) {
(method ? (method += char) : (method = char))
} else {
(arg ? (arg += char) : (arg = char))
}
}
if (char === '(') {
// nested parenthesis should be a part of arg
if (!isMethod) arg += char
isMethod = false
open.push(char)
} else if (char === ')') {
open.pop()
// check end of arg
if (open.length < 1) {
isMethod = true
ops.push({ method, arg })
method = arg = undefined
} else {
arg += char
}
}
}
return ops
}
// const test = parser(`$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)`)
const test = parser(`push(number) map(test(a(a()))) bass(wow, abc)`)
console.log(test)
wynik jest jak
[ { method: 'push', arg: 'number' },
{ method: 'map', arg: 'test(a(a()))' },
{ method: 'bass', arg: 'wow,abc' } ]
[ { method: '$$', arg: 'groups' },
{ method: 'filter',
arg: '{type:\'ORGANIZATION\',isDisabled:{$ne:true}}' },
{ method: 'pickBy', arg: '_id,type' },
{ method: 'map', arg: 'test()' },
{ method: 'as', arg: 'groups' } ]
Podczas gdy tak wiele odpowiedzi wspomina o tym w jakiejś formie, mówiąc, że regex nie obsługuje rekurencyjnego dopasowywania itd., Główny powód tego leży w korzeniach teorii obliczeń.
Język formularza {a^nb^n | n>=0} is not regular
. Regex może pasować tylko do rzeczy, które stanowią część standardowego zestawu języków.
Czytaj więcej @ tutaj
Nie użyłem wyrażenia regularnego, ponieważ trudno jest poradzić sobie z zagnieżdżonym kodem. Tak więc ten fragment kodu powinien umożliwiać przechwytywanie fragmentów kodu za pomocą zrównoważonych nawiasów:
def extract_code(data):
""" returns an array of code snippets from a string (data)"""
start_pos = None
end_pos = None
count_open = 0
count_close = 0
code_snippets = []
for i,v in enumerate(data):
if v =='{':
count_open+=1
if not start_pos:
start_pos= i
if v=='}':
count_close +=1
if count_open == count_close and not end_pos:
end_pos = i+1
if start_pos and end_pos:
code_snippets.append((start_pos,end_pos))
start_pos = None
end_pos = None
return code_snippets
Użyłem tego do wyodrębnienia fragmentów kodu z pliku tekstowego.
Ten też działał
re.findall(r'\(.+\)', s)
Może to być przydatne dla niektórych:
Tutaj możesz zobaczyć wygenerowane wyrażenie regularne w akcji
/**
* get param content of function string.
* only params string should be provided without parentheses
* WORK even if some/all params are not set
* @return [param1, param2, param3]
*/
exports.getParamsSAFE = (str, nbParams = 3) => {
const nextParamReg = /^\s*((?:(?:['"([{](?:[^'"()[\]{}]*?|['"([{](?:[^'"()[\]{}]*?|['"([{][^'"()[\]{}]*?['")}\]])*?['")}\]])*?['")}\]])|[^,])*?)\s*(?:,|$)/;
const params = [];
while (str.length) { // this is to avoid a BIG performance issue in javascript regexp engine
str = str.replace(nextParamReg, (full, p1) => {
params.push(p1);
return '';
});
}
return params;
};
Nie rozwiązuje to w pełni pytania OP, ale sądzę, że niektórzy przydadzą się tutaj, aby wyszukać wyrażenie regularne struktury zagnieżdżonej.