Wyrażenie regularne pasujące do zrównoważonych nawiasów

290

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

DaveF
źródło
3
To pytanie jest bardzo słabe, ponieważ nie jest jasne, o co je zadaje. Wszystkie odpowiedzi interpretowały to inaczej. @DaveF czy możesz wyjaśnić pytanie?
Matt Fenwick,
1
Odpowiedzi w tym poście: stackoverflow.com/questions/6331065/…
sship21

Odpowiedzi:

144

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 .

Szczery
źródło
15
Implementacja .NET ma [Równoważenie definicji grup msdn.microsoft.com/en-us/library/…, które pozwalają na tego rodzaju rzeczy.
Carl G,
22
Nie zgadzam się, że wyrażenia regularne są niewłaściwym narzędziem do tego z kilku powodów. 1) Większość implementacji wyrażeń regularnych ma wykonalne, jeśli nie idealne rozwiązanie. 2) Często próbujesz znaleźć zrównoważone pary ograniczników w kontekście, w którym obowiązują również inne kryteria dobrze dopasowane do wyrażeń regularnych. 3) Często przekazujesz wyrażenie regularne do jakiegoś API, który akceptuje tylko wyrażenia regularne i nie masz wyboru.
Kenneth Baltrinic
20
Regex to PRAWE narzędzie dla zadania. Ta odpowiedź jest nieprawidłowa. Zobacz odpowiedź rogal111.
Andrew,
4
Absolutnie zgadzam się z odpowiedzią. Chociaż istnieją pewne implementacje rekurencji w wyrażeniu regularnym, są one równe maszynom o skończonym stanie i nie są przeznaczone do pracy ze strukturami zagnieżdżonymi, ale robią to Gramatyki wolne od kontekstu. Spójrz na hierarcyzm formalnych gramatyki Homsky'ego.
Nick Roz
138

Chcę dodać tę odpowiedź w celu szybkiego odwołania. Aktualizuj.


.NET Regex przy użyciu grup bilansujących .

\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)

Gdzie cjest używany jako licznik głębokości.

Demo na Regexstorm.com


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 .


Odwołanie - co oznacza to wyrażenie regularne?

chwiejąca się bańka
źródło
1
Gdy powtarzasz grupę z kwantyfikatorem dzierżawczym, nie ma sensu uczynić tej grupy atomową, ponieważ wszystkie pozycje cofania w tej grupie są usuwane przy każdym powtórzeniu. Więc pisanie (?>[^)(]+|(?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).
Casimir et Hippolyte
Jeśli chodzi o wzorzec Ruby 1.9, zamiast przekształcić grupę atomową w powtarzaną (która ma ograniczony interes, gdy istnieje wiele zagnieżdżonych nawiasów (...(..)..(..)..(..)..(..)..)) 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.
Casimir et Hippolyte
@CasimiretHippolyte Dziękujemy! I dostosowane wzory pcre i Ruby 1.9, masz na myśli cały wzór, aby być jak ten ? Zaktualizuj się sam. Rozumiem, co masz na myśli, ale nie jestem pewien, czy nastąpiła znaczna poprawa.
bobble bubble
117

Możesz użyć rekursji wyrażenia regularnego :

\(([^()]|(?R))*\)
rogal111
źródło
3
Przykład byłby bardzo przydatny tutaj, nie mogę sprawić, żeby działał w przypadku takich rzeczy jak „(1, (2, 3)) (4, 5)”.
Andy Hayden
4
@AndyHayden dzieje się tak, ponieważ „(1, (2, 3))) (4, 5)” ma dwie grupy oddzielone spacją. Użyj wyrażenia regularnego z flagą globalną: / (([^ ()] | (? R)) *) / g. Oto test online: regex101.com/r/lF0fI1/1
rogal111
1
Zadałem pytanie o ostatni tydzień stackoverflow.com/questions/26385984/recursive-pattern-in-regex
Andy Hayden
7
W .NET 4.5 pojawia się następujący błąd dla tego wzoru: Unrecognized grouping construct.
nam
3
Niesamowite! To świetna funkcja wyrażeń regularnych. Dziękujemy, że jesteś jedyną osobą, która faktycznie odpowiedziała na pytanie. Również ta strona regex101 jest słodka.
Andrew
28
[^\(]*(\(.*\))[^\)]*

[^\(]*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 ).

Zach Scrivena
źródło
nawias wewnątrz klasy nie wymaga ucieczki. Ponieważ w środku nie jest metazakładem.
José Leal
10
Ten wyrażenie nie działa na coś takiego jak „tekst (tekst) tekst (tekst) tekst„ zwracanie ”(tekst) tekst (tekst)”. Wyrażenia regularne nie mogą liczyć nawiasów.
Christian Klauser,
17
(?<=\().*(?=\))

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.

Tomalak
źródło
Co oznaczają znaki „<=” i „=”? Jaki silnik regexp jest kierowany na to wyrażenie?
Christian Klauser
1
Jest to rozglądanie się, a ściślej „twierdzenia o zerowej szerokości z wyprzedzeniem / z tyłu”. Obsługuje je większość nowoczesnych silników wyrażeń regularnych.
Tomalak
Zgodnie z przykładem OP chce on uwzględnić w meczu najbardziej oddalone pareny. Wyrażenie regularne je wyrzuca.
Alan Moore
1
@Alan M: Masz rację. Ale zgodnie z tekstem pytania chce wszystkiego między skrajnymi parenami. Wybierz swój wybór. Powiedział, że próbował od wielu godzin, więc nawet nie wziął pod uwagę „wszystkiego, w tym skrajnych parens”, ponieważ jest to tak banalne: „(. *)”.
Tomalak
3
@ghayes Odpowiedź pochodzi z 2009 roku. To było dawno temu; mechanizmy wyrażeń regularnych, które pozwalają na pewną formę rekurencji, były bardziej rzadkie niż obecnie (i nadal są dość rzadkie). Wspomnę o tym w mojej odpowiedzi.
Tomalak,
14

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 FSAmoże zapamiętać tylko bieżący stan, nie ma informacji o poprzednich stanach.

FSA

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 01z 0110FSA nie ma informacji o poprzednim 0w 01jak 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ż FSAsnie 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). PDAjest 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.

musibs
źródło
1
Jest tu kilka odpowiedzi, które dowodzą, że jest to możliwe.
Jiří Herník,
1
@Marco Ta odpowiedź mówi o wyrażeniach regularnych w ujęciu teoretycznym. Wiele silników wyrażeń regularnych obecnie opiera się nie tylko na tym modelu teoretycznym i wykorzystuje dodatkową pamięć do wykonania zadania!
musibs
@ JiříHerník: nie są to wyrażenia regularne w ścisłym tego słowa znaczeniu: nie zdefiniowane przez Kleene jako wyrażenia regularne . Niektóre silniki wyrażeń regularnych rzeczywiście mają zaimplementowane dodatkowe funkcje, dzięki czemu parsują więcej niż tylko zwykłe języki .
Willem Van Onsem,
12

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)(?!))
>
Alexander Bartosh
źródło
4

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

Marco
źródło
4

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 .

Chad Scira
źródło
4

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 .

reRozwią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)))]
Wiktor Stribiżew
źródło
Zobacz demonstrację online Java dla dowodu, że działa z wieloma dopasowaniami.
Wiktor Stribiżew
3

Wyrażenie regularne używające Ruby (wersja 1.9.3 lub nowsza):

/(?<match>\((?:\g<match>|[^()]++)*\))/

Demo na rubularach

Joy Hu
źródło
3

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(')');
Shell Scott
źródło
1
"""
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"
Gene Olson
źródło
0

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ł.

Douglas Leeder
źródło
3
To nie jest odpowiedź.
Alan Moore,
Tak, żądanie zmiany pytania powinno być podane w formie komentarza,
Gangnus
0

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' } ]
crapthings
źródło
0

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

Prakhar Agrawal
źródło
0

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.

Daniel
źródło
0

Utknąłem również w sytuacji, w której pojawiają się wzory zagnieżdżone.

Wyrażenie regularne jest właściwym rozwiązaniem powyższego problemu. Użyj poniższego wzoru

'/(\((?>[^()]+|(?1))*\))/'
Manish
źródło
-1

Ten też działał

re.findall(r'\(.+\)', s)
DataScienceStep
źródło
-1

Może to być przydatne dla niektórych:

Analizuj parmetery z ciągu funkcji (ze strukturami zagnieżdżonymi) w javascript

Dopasuj struktury, takie jak:
Analizuj parmetery z ciągu funkcji

  • dopasowuje nawiasy kwadratowe, nawiasy kwadratowe, pojedyncze i podwójne cudzysłowy

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.

538ROMEO
źródło