Regulärer Ausdruck passend zu ausgewogenen Klammern


290

Ich benötige einen regulären Ausdruck, um den gesamten Text zwischen zwei äußeren Klammern auszuwählen.

Beispiel: some text(text here(possible text)text(possible text(more text)))end text

Ergebnis: (text here(possible text)text(possible text(more text)))


3
Diese Frage ist sehr schlecht, weil nicht klar ist, was sie stellt. Alle Antworten interpretierten es unterschiedlich. @ DaveF kannst du bitte die Frage klären?
Matt Fenwick

1
Beantwortet in diesem Beitrag: stackoverflow.com/questions/6331065/…
sship21

Antworten:


144

Reguläre Ausdrücke sind das falsche Werkzeug für den Job, da es sich um verschachtelte Strukturen handelt, dh um Rekursion.

Dafür gibt es jedoch einen einfachen Algorithmus, den ich in dieser Antwort auf eine vorherige Frage beschrieben habe .


15
Die Implementierung von .NET verfügt über [Balancing Group Definitions msdn.microsoft.com/en-us/library/…, die dies ermöglichen.
Carl G

22
Ich bin nicht der Meinung, dass reguläre Ausdrücke aus mehreren Gründen das falsche Werkzeug sind. 1) Die meisten Implementierungen mit regulären Ausdrücken haben eine praktikable, wenn nicht perfekte Lösung dafür. 2) Oft versuchen Sie, ausgewogene Trennzeichenpaare in einem Kontext zu finden, in dem auch andere Kriterien im Spiel sind, die für reguläre Ausdrücke gut geeignet sind. 3) Oft übergeben Sie einen regulären Ausdruck an eine API, die nur reguläre Ausdrücke akzeptiert, und Sie haben keine Wahl.
Kenneth Baltrinic


20
Regex ist das RICHTIGE Werkzeug für den Job. Diese Antwort ist nicht richtig. Siehe die Antwort von rogal111.
Andrew

4
Stimme der Antwort absolut zu. Obwohl es in Regexp einige Implementierungen der Rekursion gibt, entsprechen sie Finite-State-Maschinen und werden nicht für die Arbeit mit verschachtelten Strukturen unterstützt, aber kontextfreie Grammatiken tun dies. Schauen Sie sich Homskys Hierarchie formaler Grammatiken an.
Nick Roz

138

Ich möchte diese Antwort zur schnellen Referenz hinzufügen. Fühlen Sie sich frei zu aktualisieren.


.NET Regex mit Ausgleichsgruppen .

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

Wo cwird als Tiefenzähler verwendet.

Demo bei Regexstorm.com


PCRE unter Verwendung eines rekursiven Musters .

\((?:[^)(]+|(?R))*+\)

Demo bei regex101 ; Oder ohne Abwechslung:

\((?:[^)(]*(?R)?)*+\)

Demo bei regex101 ; Oder für die Leistung abgerollt :

\([^)(]*+(?:(?R)[^)(]*)*+\)

Demo bei regex101 ; Das Muster wird eingefügt, bei (?R)dem dargestellt wird (?0).

Perl, PHP, Notepad ++, R : Perl = TRUE , Python : Regex-Paket mit (?V1)für Perl-Verhalten.


Ruby verwendet Unterausdrucksaufrufe .

Mit Ruby 2.0 \g<0>kann das vollständige Muster aufgerufen werden.

\((?>[^)(]+|\g<0>)*\)

Demo bei Rubular ; Ruby 1.9 unterstützt nur die Erfassung der Gruppenrekursion :

(\((?>[^)(]+|\g<1>)*\))

Demo bei Rubular  ( Atomgruppierung seit Ruby 1.9.3)


JavaScript  API :: XRegExp.matchRecursive

XRegExp.matchRecursive(str, '\\(', '\\)', 'g');

JS-, Java- und andere Regex-Varianten ohne Rekursion bis zu 2 Verschachtelungsebenen:

\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)

Demo bei regex101 . Dem Muster muss eine tiefere Verschachtelung hinzugefügt werden .
Um bei unausgeglichenen Klammern schneller zu versagen, lassen Sie den +Quantifizierer fallen.


Java : Eine interessante Idee mit Forward-Referenzen von @jaytea .


Referenz - Was bedeutet dieser Regex?


1
Wenn Sie eine Gruppe mit einem besitzergreifenden Quantifizierer wiederholen, ist es sinnlos, diese Gruppe atomar zu machen, da alle Backtracking-Positionen in dieser Gruppe bei jeder Wiederholung gelöscht werden. Schreiben (?>[^)(]+|(?R))*+ist also dasselbe wie Schreiben (?:[^)(]+|(?R))*+. Gleiches gilt für das nächste Muster. [^)(]*+In Bezug auf die entrollte Version können Sie hier einen Possessivquantifizierer einfügen: um ein Zurückverfolgen zu verhindern (falls es keine schließende Klammer gibt).
Casimir et Hippolyte

In Bezug auf das Ruby 1.9-Muster können Sie, anstatt die wiederholte Gruppe atomar zu machen (was bei vielen verschachtelten Klammern ein begrenztes Interesse hat (...(..)..(..)..(..)..(..)..)), eine einfache nicht erfassende Gruppe verwenden und alle in eine atomare Gruppe einschließen: (?>(?:[^)(]+|\g<1>)*)( Dies verhält sich genau wie ein Possessivquantifizierer. In Ruby 2.x ist der Possessivquantifizierer verfügbar.
Casimir et Hippolyte

@ CasimiretHippolyte Danke! Ich stellte das PCRE - Muster und für Ruby 1.9, meinst du das ganze Muster zu sein , wie dies ? Bitte zögern Sie nicht, sich selbst zu aktualisieren. Ich verstehe, was Sie meinen, bin mir aber nicht sicher, ob es viel Verbesserung gibt.
Bobble Bubble

117

Sie können die Regex-Rekursion verwenden :

\(([^()]|(?R))*\)

3
Ein Beispiel wäre hier wirklich nützlich, ich kann dies nicht für Dinge wie "(1, (2, 3)) (4, 5)" zum Laufen bringen.
Andy Hayden

4
@AndyHayden Dies liegt daran, dass "(1, (2, 3)) (4, 5)" zwei durch Leerzeichen getrennte Gruppen hat. Verwenden Sie meinen regulären Ausdruck mit dem globalen Flag: / (([^ ()] | (? R)) *) / g. Hier ist Online-Test: regex101.com/r/lF0fI1/1
rogal111


7
In .NET 4.5 wird für dieses Muster der folgende Fehler angezeigt : Unrecognized grouping construct.
Nam

3
Genial! Dies ist eine großartige Funktion von Regex. Vielen Dank, dass Sie der einzige sind, der die Frage tatsächlich beantwortet. Auch diese regex101-Site ist süß.
Andrew

28
[^\(]*(\(.*\))[^\)]*

[^\(]*Entspricht allem, was keine öffnende Klammer am Anfang der Zeichenfolge ist, (\(.*\))erfasst die erforderliche Teilzeichenfolge in Klammern und [^\)]*stimmt mit allem überein, was keine schließende Klammer am Ende der Zeichenfolge ist. Beachten Sie, dass dieser Ausdruck nicht versucht, mit Klammern übereinzustimmen. Ein einfacher Parser (siehe Dehmanns Antwort ) wäre dafür besser geeignet.


Die Klammer innerhalb der Klasse muss nicht maskiert werden. Da es sich im Inneren nicht um einen Metacharakt handelt.
José Leal

10
Dieser Ausdruck schlägt fehl gegen etwas wie "Text (Text) Text (Text) Text" Rückgabe "(Text) Text (Text)". Reguläre Ausdrücke können keine Klammern zählen.
Christian Klauser

17
(?<=\().*(?=\))

Wenn Sie Text zwischen zwei übereinstimmenden Klammern auswählen möchten , haben Sie mit regulären Ausdrücken kein Glück. Dies ist unmöglich (*) .

Diese Regex gibt nur den Text zwischen der ersten öffnenden und der letzten schließenden Klammer in Ihrer Zeichenfolge zurück.


(*) Es sei denn, Ihre Regex-Engine verfügt über Funktionen wie Ausgleichsgruppen oder Rekursion . Die Anzahl der Motoren, die solche Funktionen unterstützen, wächst langsam, ist aber immer noch nicht allgemein verfügbar.


Was bedeuten die Zeichen "<=" und "="? Auf welche Regexp-Engine zielt dieser Ausdruck ab?
Christian Klauser

1
Dies ist ein Umsehen oder genauer gesagt "Vorausschau- / Rückblick-Behauptungen mit einer Breite von Null". Die meisten modernen Regex-Motoren unterstützen sie.
Tomalak

Nach dem Beispiel des OP möchte er die äußersten Eltern in das Spiel einbeziehen. Diese Regex wirft sie weg.
Alan Moore

1
@ Alan M: Du hast recht. Aber laut Fragetext will er alles zwischen den äußersten Eltern. Wählen Sie Ihre Wahl. Er sagte, er habe es stundenlang versucht, also habe er nicht einmal "alles einschließlich der äußersten Eltern" als Absicht angesehen, weil es so trivial sei: "(. *)".
Tomalak

3
@ghayes Die Antwort stammt aus dem Jahr 2009. Das ist lange her; Engines für reguläre Ausdrücke, die eine Form der Rekursion zulassen, waren seltener als jetzt (und sie sind immer noch ziemlich ungewöhnlich). Ich werde es in meiner Antwort erwähnen.
Tomalak

14

Diese Antwort erklärt die theoretische Einschränkung, warum reguläre Ausdrücke nicht das richtige Werkzeug für diese Aufgabe sind.


Reguläre Ausdrücke können dies nicht.

Reguläre Ausdrücke basieren auf einem Rechenmodell, das als bekannt ist Finite State Automata (FSA). Wie der Name schon sagt, FSAkann sich a nur an den aktuellen Status erinnern, es enthält keine Informationen zu den vorherigen Status.

FSA

Im obigen Diagramm sind S1 und S2 zwei Zustände, in denen S1 der Start- und Endschritt ist. Wenn wir es also mit der Zeichenfolge versuchen, läuft 0110der Übergang wie folgt ab:

      0     1     1     0
-> S1 -> S2 -> S2 -> S2 ->S1

In den obigen Schritten hat die FSA , wenn wir uns auf dem zweiten Platz befinden, S2dh nach dem Parsen 01von 0110, keine Informationen über den vorherigen 0in, 01da sie sich nur an den aktuellen Status und das nächste Eingabesymbol erinnern kann.

In dem obigen Problem müssen wir die Anzahl der öffnenden Klammern kennen; Dies bedeutet , es muss gespeichert an einem Ort. Da dies FSAsaber nicht möglich ist, kann kein regulärer Ausdruck geschrieben werden.

Für diese Aufgabe kann jedoch ein Algorithmus geschrieben werden. Algorithmen fallen in der Regel unter Pushdown Automata (PDA). PDAist eine Ebene über FSA. Der PDA verfügt über einen zusätzlichen Stapel zum Speichern einiger zusätzlicher Informationen. PDAs können verwendet werden, um das obige Problem zu lösen, da wir pushdie öffnende Klammer im Stapel und popsie öffnen können, sobald wir auf eine schließende Klammer stoßen. Wenn der Stapel am Ende leer ist, stimmen das Öffnen der Klammer und das Schließen der Klammer überein. Sonst nicht.



1
Hier gibt es mehrere Antworten, die beweisen, dass es möglich ist.
Jiří Herník

1
@Marco Diese Antwort spricht über reguläre Ausdrücke in theoretischer Perspektive. Viele Regex-Engines verlassen sich heutzutage nicht nur auf dieses theoretische Modell und verwenden zusätzlichen Speicher, um die Arbeit zu erledigen!
Musibs

@ JiříHerník: das sind keine regulären Ausdrücke im engeren Sinne: von Kleene nicht als reguläre Ausdrücke definiert . Einige Engines für reguläre Ausdrücke haben in der Tat einige zusätzliche Funktionen implementiert, sodass sie mehr als nur reguläre Sprachen analysieren .
Willem Van Onsem

12

Es ist tatsächlich möglich, dies mit regulären .NET-Ausdrücken zu tun, aber es ist nicht trivial. Lesen Sie es daher sorgfältig durch.

Sie können einen schönen Artikel lesen Sie hier . Möglicherweise müssen Sie auch reguläre .NET-Ausdrücke nachlesen. Sie können anfangen zu lesen hier .

Winkelklammern <>wurden verwendet, da sie kein Entweichen erfordern.

Der reguläre Ausdruck sieht folgendermaßen aus:

<
[^<>]*
(
    (
        (?<Open><)
        [^<>]*
    )+
    (
        (?<Close-Open>>)
        [^<>]*
    )+
)*
(?(Open)(?!))
>

4

Dies ist der endgültige Regex:

\(
(?<arguments> 
(  
  ([^\(\)']*) |  
  (\([^\(\)']*\)) |
  '(.*?)'

)*
)
\)

Beispiel:

input: ( arg1, arg2, arg3, (arg4), '(pip' )

output: arg1, arg2, arg3, (arg4), '(pip'

Beachten Sie, dass die '(pip'Zeichenfolge korrekt verwaltet wird. (versucht in Regulierungsbehörde: http://sourceforge.net/projects/regulator/ )


4

Ich habe eine kleine JavaScript-Bibliothek namens Balanced geschrieben , um bei dieser Aufgabe zu helfen. Sie können dies erreichen, indem Sie dies tun

balanced.matches({
    source: source,
    open: '(',
    close: ')'
});

Sie können sogar ersetzen:

balanced.replacements({
    source: source,
    open: '(',
    close: ')',
    replace: function (source, head, tail) {
        return head + source + tail;
    }
});

Hier ist ein komplexeres und interaktiveres Beispiel für JSFiddle .


4

Zusätzlich zur Antwort von Bobble Bubble gibt es andere Regex-Varianten, bei denen rekursive Konstrukte unterstützt werden.

Lua

Verwendung %b()( %b{}/ %b[]für geschweifte Klammern / eckige Klammern):

  • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end(siehe Demo )

Perl6 :

Nicht überlappende Übereinstimmungen mit mehreren ausgeglichenen Klammern:

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

Überlappende Übereinstimmungen mit mehreren ausgeglichenen Klammern:

say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)

Siehe Demo .

Python- reNicht-Regex-Lösung

Siehe die Antwort von poke für Wie man einen Ausdruck zwischen ausgeglichenen Klammern erhält .

Java anpassbare Nicht-Regex-Lösung

Hier ist eine anpassbare Lösung, die Literaltrennzeichen für einzelne Zeichen in Java ermöglicht:

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

Beispielnutzung:

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

In einer Online-Java-Demo finden Sie einen Beweis dafür, dass es mit mehreren Übereinstimmungen funktioniert.
Wiktor Stribiżew


3

Sie benötigen die erste und letzte Klammer. Verwenden Sie so etwas:

str.indexOf ('('); - gibt Ihnen das erste Auftreten

str.lastIndexOf (')'); - Letzter

Sie benötigen also eine Zeichenfolge zwischen,

String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');

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"

0

Die Antwort hängt davon ab, ob Sie übereinstimmende Klammern oder nur das erste Öffnen bis zum letzten Schließen im Eingabetext abgleichen müssen.

Wenn Sie mit übereinstimmenden verschachtelten Klammern übereinstimmen müssen, benötigen Sie mehr als reguläre Ausdrücke. - siehe @dehmann

Wenn es nur vom ersten bis zum letzten Schließen geöffnet ist, siehe @Zach

Entscheiden Sie, womit Sie passieren möchten:

abc ( 123 ( foobar ) def ) xyz ) ghij

Sie müssen entscheiden, was Ihr Code in diesem Fall übereinstimmen muss.


3
Dies ist keine Antwort.
Alan Moore

Ja, die Forderung nach einer Änderung der Frage sollte als Kommentar gegeben werden
Gangnus

0

Da js regex keine rekursive Übereinstimmung unterstützt, kann ich keine Übereinstimmung mit ausgeglichenen Klammern erzielen.

Dies ist also ein einfaches Javascript für die Schleifenversion, das die Zeichenfolge "method (arg)" in ein Array umwandelt

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)

Das Ergebnis ist wie

[ { 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' } ]

0

Während so viele Antworten dies in irgendeiner Form erwähnen, indem sie sagen, dass Regex kein rekursives Matching usw. unterstützt, liegt der Hauptgrund dafür in den Wurzeln der Berechnungstheorie.

Sprache der Form {a^nb^n | n>=0} is not regular. Regex kann nur mit Dingen übereinstimmen, die Teil des regulären Satzes von Sprachen sind.

Lesen Sie mehr @ hier


0

Ich habe keinen regulären Ausdruck verwendet, da es schwierig ist, mit verschachteltem Code umzugehen. Mit diesem Snippet sollten Sie also Codeabschnitte mit ausgewogenen Klammern abrufen können:

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

Ich habe dies verwendet, um Codefragmente aus einer Textdatei zu extrahieren.


0

Ich war auch in dieser Situation festgefahren, in der verschachtelte Muster auftreten.

Regulärer Ausdruck ist das Richtige, um das oben genannte Problem zu lösen. Verwenden Sie das folgende Muster

'/(\((?>[^()]+|(?1))*\))/'


-1

Dies kann für einige nützlich sein:

Analysieren Sie Parameter aus Funktionszeichenfolgen (mit verschachtelten Strukturen) in Javascript

Übereinstimmende Strukturen wie:
Analysieren Sie Parameter aus der Funktionszeichenfolge

  • stimmt mit Klammern, eckigen Klammern, Klammern, einfachen und doppelten Anführungszeichen überein

Hier können Sie den generierten regulären Ausdruck in Aktion sehen

/**
 * 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;
};

Dies behandelt die OP-Frage nicht vollständig, aber ich denke, dass es für einige, die hierher kommen, nützlich sein kann, nach Regexp für verschachtelte Strukturen zu suchen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.