2016-03-29 21 views
8

Ich arbeite an einem Projekt in Java, das geschachtelte Zeichenfolgen erfordert.Teilen einer geschachtelten Zeichenfolge, die Anführungszeichen enthält

Für eine Eingabezeichenfolge, die wie folgt im Klartext aussieht:

Das ist „ein String“ und dies ist „ein \“ \ „string“

verschachtelt sein

Das Ergebnis muss die folgenden:

[0] This 
[1] is 
[2] "a string" 
[3] and 
[4] this 
[5] is 
[6] "a \"nested\" string" 

Hinweis, dass ich möchte, dass die \" Sequenzen gehalten werden.
Ich habe die folgende Methode:

public static String[] splitKeepingQuotationMarks(String s); 

und ich brauche ein Array von Strings aus den gegebenen s Parametern durch die gegebenen Regeln zu schaffen, ohne die Verwendung des Java Collection Framework oder seine Derivate.

Ich bin unsicher, wie Sie dieses Problem lösen können.
Kann ein Regex-Ausdruck erstellt werden, der dies lösen würde?

UPDATE basierend auf Fragen von Kommentaren:

  • jeder unescaped " hat seine Schließung unescaped " (sie sind symmetrisch)
  • jede Flucht Charakter \ auch maskiert werden müssen, wenn wir wörtliche erstellen möchten darstellen es (um Text zu erstellen, der \ darstellt, müssen wir es als \\ schreiben).
+0

@Turtle: Nicht immer. Es teilt die 'geschachtelte' Zeichenfolge auch auf. –

+0

auch wenn Sie auf ein Leerzeichen aufteilen? – Turtle

+0

Das ist keine normale Sprache. Sie benötigen Funktionen, die über normale reguläre Ausdrücke hinausgehen. Look-arounds erweitern Regex über normale Sprachen hinaus, aber da dies wie eine Schulaufgabe klingt, könnte das Ziel darin bestehen, einen Lexer (lexikalischen Analysator) zu schreiben. – jpmc26

Antwort

7

Sie können die folgende regex verwenden:

"[^"\\]*(?:\\.[^"\\]*)*"|\S+ 

Siehe regex demo

Java demo:

String str = "This is \"a string\" and this is \"a \\\"nested\\\" string\""; 
Pattern ptrn = Pattern.compile("\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|\\S+"); 
Matcher matcher = ptrn.matcher(str); 
while (matcher.find()) { 
    System.out.println(matcher.group(0)); 
} 

Erklärung:

  • "[^"\\]*(?:\\.[^"\\]*)*" - einem doppelten Anführungszeichen, die mit jedem 0+ andere Zeichen als ein " und \ ([^"\\]) und anschließend mit 0+ Sequenzen von jeder entkommen Sequenz (\\.) und anschließend mit beliebigen 0+ andere Zeichen als ein " und \ folgt
  • | - oder ...
  • \S+-1 oder mehr Nicht-Leerzeichen

HINWEIS

@Pshemo's suggestion - "\"(?:\\\\.|[^\"])*\"|\\S+" (oder "\"(?:\\\\.|[^\"\\\\])*\"|\\S+" wäre richtig) - ist der gleiche Ausdruck, aber viel weniger effizient, da es verwendet eine Alternationsgruppe, quantifiziert mit *. Dieses Konstrukt beinhaltet viel mehr Rückverfolgung, da die Regex-Engine jede Position testen muss, und es gibt 2 Wahrscheinlichkeiten für jede Position. Meine unroll-the-loop basierte Version wird Textstücke auf einmal zusammenpassen und ist daher viel schneller und zuverlässiger.

UPDATE

Da String[] Typ als Ausgabe erforderlich ist, müssen Sie es in 2 Schritten tun: die Spiele zählen, erstellen Sie das Array, und dann wieder die Matcher erneut ausführen:

int cnt = 0; 
String str = "This is \"a string\" and this is \"a \\\"nested\\\" string\""; 
Pattern ptrn = Pattern.compile("\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|\\S+"); 
Matcher matcher = ptrn.matcher(str); 
while (matcher.find()) { 
    cnt++; 
} 
System.out.println(cnt); 
String[] result = new String[cnt]; 
matcher.reset(); 
int idx = 0; 
while (matcher.find()) { 
    result[idx] = matcher.group(0); 
    idx++; 
} 
System.out.println(Arrays.toString(result)); 

Siehe another IDEONE demo

+0

WTF ...! Wie hast du das gemacht ..! +1 – Shafizadeh

+2

@Shafizadeh Ich fügte die Erklärung hinzu, und jetzt die comp zu meiner nagenden Frau :) –

+0

'Pattern.compile (" \ "(?: \\\\. | [^ \"]) * \ "| \ \ S + ");" sollte wahrscheinlich auch funktionieren. – Pshemo

7

ein weiterer regex Ansatz, der eine negative Lookbehind arbeitet verwendet: "Wörter" (\w+) ORZitat von irgendetwas gefolgt bis zum nächsten Zitat, das von einem umgekehrten Schrägstrich nicht vorangestellt ist“, und setzen Sie Ihr Spiel auf „global“ (nicht auf dem ersten Spiel zurückkehren)

(\w+|".*?(?<!\\)") 

see it here.

+1

Das ist ein nettes Muster, +1 – Shafizadeh

+0

Aber wie gehst du von einem Token Regex zu einem Array von Übereinstimmungen, ohne eine 'List' zu verwenden? Die Split-APIs verwenden einen Delimiter-Ausdruck und keinen Token-Ausdruck. – erickson

+1

@erickson: nicht sicher, was du meinst ..? – sweaver2112

1

Ein alternatives Verfahren, das nicht einen regulären Ausdruck nicht verwendet:

import java.util.ArrayList; 
import java.util.Arrays; 

public class SplitKeepingQuotationMarks { 
    public static void main(String[] args) { 
     String pattern = "This is \"a string\" and this is \"a \\\"nested\\\" string\""; 
     System.out.println(Arrays.toString(splitKeepingQuotationMarks(pattern))); 
    } 

    public static String[] splitKeepingQuotationMarks(String s) { 
     ArrayList<String> results = new ArrayList<>(); 
     StringBuilder last = new StringBuilder(); 
     boolean inString = false; 
     boolean wasBackSlash = false; 

     for (char c : s.toCharArray()) { 
      if (Character.isSpaceChar(c) && !inString) { 
       if (last.length() > 0) { 
        results.add(last.toString()); 
        last.setLength(0); // Clears the s.b. 
       } 
      } else if (c == '"') { 
       last.append(c); 
       if (!wasBackSlash) 
        inString = !inString; 
      } else if (c == '\\') { 
       wasBackSlash = true; 
       last.append(c); 
      } else 
       last.append(c); 
     } 

     results.add(last.toString()); 
     return results.toArray(new String[results.size()]); 
    } 
} 

Output:

[Dies ist, "a string", und dies ist, "a \" nested \ "string"]

+0

'import java.util.ArrayList;' -> "ohne Verwendung der Java Collection Framework oder seine Derivate. " – Pshemo

+0

@Pshemo Oh. Habe diesen Teil nicht gelesen ... – Majora320

Verwandte Themen