it-swarm.com.de

Aufteilung außerhalb der Anführungszeichen

Mein Programm liest eine Zeile aus einer Datei. Diese Zeile enthält durch Kommas getrennten Text wie:

123,test,444,"don't split, this",more test,1

Ich möchte, dass das Ergebnis einer Spaltung Folgendes ist:

123
test
444
"don't split, this"
more test
1

Wenn ich die String.split(",") benutze, würde ich folgendes bekommen:

123
test
444
"don't split
 this"
more test
1

Mit anderen Worten: Das Komma in der Teilzeichenfolge "don't split, this" ist kein Trennzeichen. Wie gehe ich damit um?

Vielen Dank im Voraus ... Jakob

38
Jakob Mathiasen

Sie können diese Regex ausprobieren:

str.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

Dadurch wird die Zeichenfolge in , aufgeteilt, auf die eine gerade Anzahl von Anführungszeichen folgt. Mit anderen Worten, es wird außerhalb der Anführungszeichen in Kommas aufgeteilt. Dies funktioniert, sofern Sie in Ihrer Zeichenfolge ausgeglichene Anführungszeichen haben.

Erläuterung:

,           // Split on comma
(?=         // Followed by
   (?:      // Start a non-capture group
     [^"]*  // 0 or more non-quote characters
     "      // 1 quote
     [^"]*  // 0 or more non-quote characters
     "      // 1 quote
   )*       // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
   [^"]*    // Finally 0 or more non-quotes
   $        // Till the end  (This is necessary, else every comma will satisfy the condition)
)

Sie können sogar so in Ihren Code eingeben, indem Sie (?x)-Modifikator für Ihren regulären Ausdruck verwenden. Der Modifikator ignoriert alle Whitespaces in Ihrer Regex, sodass es einfacher wird, einen Regex in mehrere Zeilen zu zerlegen, z.

String[] arr = str.split("(?x)   " + 
                     ",          " +   // Split on comma
                     "(?=        " +   // Followed by
                     "  (?:      " +   // Start a non-capture group
                     "    [^\"]* " +   // 0 or more non-quote characters
                     "    \"     " +   // 1 quote
                     "    [^\"]* " +   // 0 or more non-quote characters
                     "    \"     " +   // 1 quote
                     "  )*       " +   // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
                     "  [^\"]*   " +   // Finally 0 or more non-quotes
                     "  $        " +   // Till the end  (This is necessary, else every comma will satisfy the condition)
                     ")          "     // End look-ahead
                         );
82
Rohit Jain

Warum Split, wenn Sie zusammenpassen können?

Wiederbelebung dieser Frage, weil die einfache Lösung aus irgendeinem Grund nicht erwähnt wurde. Hier ist unser schön kompakter Regex:

"[^"]*"|[^,]+

Dies entspricht allen gewünschten Fragmenten ( siehe Demo ).

Erklärung

  • Mit "[^"]*" stimmen wir vollständig "double-quoted strings" überein.
  • oder |
  • wir stimmen mit [^,]+ allen Zeichen überein, die kein Komma sind.

Eine mögliche Verfeinerung besteht darin, die Zeichenfolgenseite der Änderung zu verbessern, um zu ermöglichen, dass die zitierten Zeichenfolgen mit Escapezeichen versehene Anführungszeichen enthalten.

8
zx81

Dies ist sehr einfach ohne komplexe reguläre Ausdrücke möglich:

  1. Teilen Sie das Zeichen " auf. Sie erhalten eine Liste mit Strings
  2. Bearbeiten Sie jeden String in der Liste: Trennen Sie jeden String, der sich an einer geraden Position in der Liste befindet (beginnt mit der Indizierung mit Null), auf "," (Sie erhalten eine Liste in einer Liste), lassen Sie jeden ungeraden positionierten String in Ruhe (direkt einfügen) eine Liste innerhalb der Liste).
  3. Treten Sie der Liste der Listen bei, sodass Sie nur eine Liste erhalten.

Wenn Sie das Zitat von '' 'verwenden möchten, müssen Sie den Algorithmus ein wenig anpassen (einige Teile zusammenfügen, Sie haben falsch aufgeteilt oder die Aufteilung in einfachen regexp geändert), aber die grundlegende Struktur bleibt erhalten.

Im Grunde ist es also so:

public class SplitTest {
    public static void main(String[] args) {
        final String splitMe="123,test,444,\"don't split, this\",more test,1";
        final String[] splitByQuote=splitMe.split("\"");
        final String[][] splitByComma=new String[splitByQuote.length][];
        for(int i=0;i<splitByQuote.length;i++) {
            String part=splitByQuote[i];
            if (i % 2 == 0){
               splitByComma[i]=part.split(",");
            }else{
                splitByComma[i]=new String[1];
                splitByComma[i][0]=part;
            }
        }
        for (String parts[] : splitByComma) {
            for (String part : parts) {
                System.out.println(part);
            }
        }
    }
}

Dies wird mit Lambdas viel sauberer sein, versprochen!

1

Aufbauend auf @ zx81s answer, ist die passende Idee wirklich schön. Ich habe Java 9resultscall hinzugefügt, das eine Stream zurückgibt. Da OP split verwenden wollte, habe ich String[] gesammelt, wie split dies tut.

Vorsicht, wenn Sie nach Ihren Komma-Trennzeichen Leerzeichen haben (a, b, "c,d"). Dann müssen Sie das Muster ändern.

Jshell Demo

$ jshell
-> String so = "123,test,444,\"don't split, this\",more test,1";
|  Added variable so of type String with initial value "123,test,444,"don't split, this",more test,1"

-> Pattern.compile("\"[^\"]*\"|[^,]+").matcher(so).results();
|  Expression value is: [email protected]
|    assigned to temporary variable $68 of type Java.util.stream.Stream<MatchResult>

-> $68.map(MatchResult::group).toArray(String[]::new);
|  Expression value is: [Ljava.lang.String;@6b09bb57
|    assigned to temporary variable $69 of type String[]

-> Arrays.stream($69).forEach(System.out::println);
123
test
444
"don't split, this"
more test
1

Code

String so = "123,test,444,\"don't split, this\",more test,1";
Pattern.compile("\"[^\"]*\"|[^,]+")
    .matcher(so)
    .results()
    .map(MatchResult::group)
    .toArray(String[]::new);

Erläuterung

  1. Regex [^"] passt zusammen: ein Zitat, alles andere als ein Zitat, ein Zitat.
  2. Regex [^"]* passt zusammen: ein Zitat, alles andere als ein Zitat 0 (oder mehr), ein Zitat.
  3. Dieser Regex muss zuerst "win" gehen, ansonsten würde alles andere als ein Komma 1 oder mehr - also [^,]+ - "gewinnen".
  4. results() erfordert Java 9 oder höher.
  5. Es gibt Stream<MatchResult> zurück, den ich mit group() aufrufe und in einem Array von Strings sammle. Der parameterlose Aufruf von toArray() würde Object[] zurückgeben.

Bitte sehen Sie den Code-Ausschnitt unten. Dieser Code berücksichtigt nur Happy Flow. Ändern Sie das entsprechend Ihrer Anforderung

public static String[] splitWithEscape(final String str, char split,
        char escapeCharacter) {
    final List<String> list = new LinkedList<String>();

    char[] cArr = str.toCharArray();

    boolean isEscape = false;
    StringBuilder sb = new StringBuilder();

    for (char c : cArr) {
        if (isEscape && c != escapeCharacter) {
            sb.append(c);
        } else if (c != split && c != escapeCharacter) {
            sb.append(c);
        } else if (c == escapeCharacter) {
            if (!isEscape) {
                isEscape = true;
                if (sb.length() > 0) {
                    list.add(sb.toString());
                    sb = new StringBuilder();
                }
            } else {
                isEscape = false;
            }

        } else if (c == split) {
            list.add(sb.toString());
            sb = new StringBuilder();
        }
    }

    if (sb.length() > 0) {
        list.add(sb.toString());
    }

    String[] strArr = new String[list.size()];

    return list.toArray(strArr);
}
0