web-dev-qa-db-fra.com

Fractionnement sur virgule en dehors des guillemets

Mon programme lit une ligne d'un fichier. Cette ligne contient du texte séparé par des virgules comme:

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

J'aimerais que le résultat d'une scission soit le suivant:

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

Si j'utilise le String.split(","), j'obtiendrais ceci:

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

En d'autres termes: la virgule dans la sous-chaîne "don't split, this" n'est pas un séparateur. Comment gérer cela?

Merci d'avance .. Jakob

38
Jakob Mathiasen

Vous pouvez essayer cette regex:

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

Cela divise la chaîne sur , suivie d'un nombre pair de guillemets doubles. En d'autres termes, il se divise par virgule en dehors des guillemets. Cela fonctionnera à condition d'avoir des citations équilibrées dans votre chaîne.

Explication:

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

Vous pouvez même taper comme ceci dans votre code, en utilisant (?x) modificateur avec votre regex. Le modificateur ignore les espaces dans votre expression rationnelle, il est donc plus facile de lire une expression rationnelle divisée en plusieurs lignes, comme ceci:

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

Pourquoi diviser quand vous pouvez faire correspondre?

Ressusciter cette question parce que, pour une raison quelconque, la solution facile n'a pas été mentionnée. Voici notre regex magnifiquement compacte:

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

Cela correspondra à tous les fragments souhaités ( voir demo ).

Explication

  • Avec "[^"]*", nous correspondons à "double-quoted strings" complet
  • ou |
  • nous correspondons à [^,]+ tous les caractères qui ne sont pas une virgule.

Un raffinement possible consiste à améliorer le côté chaîne de l'alternance pour permettre aux chaînes citées d'inclure des citations échappées.

8
zx81

Vous pouvez le faire très facilement sans expression régulière complexe:

  1. Fractionner sur le caractère ". Vous obtenez une liste de cordes
  2. Traitez chaque chaîne de la liste: Scindez chaque chaîne qui occupe une position paire dans la liste (à partir de l'indexation avec zéro) sur "," (vous obtenez une liste dans une liste), laissez chaque chaîne impaire positionnée seule (en la mettant directement dans une liste à l'intérieur de la liste).
  3. Rejoignez la liste des listes pour ne recevoir qu'une liste.

Si vous voulez gérer les guillemets de '"', vous devez adapter un peu l'algorithme (en joignant certaines parties, vous avez scindé de manière incorrecte ou vous avez modifié la scission en regexp simple), mais la structure de base reste.

Donc, fondamentalement, cela ressemble à ceci:

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

Ce sera beaucoup plus propre avec les lambdas, promis!

1
stefan.schwetschke

S'appuyant sur @ zx81 answer, parce que l'idée correspondante est vraiment agréable, j'ai ajouté Java 9resultscall, qui renvoie Stream. Depuis que OP souhaitait utiliser split, j'ai collecté pour String[], comme le fait split.

Attention si vous avez des espaces après vos séparateurs de virgule (a, b, "c,d"). Ensuite, vous devez changer le motif.

Jshell démo

$ 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: Java.util.stream.ReferencePipeline$Head@2038ae61
|    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);

Explication

  1. Regex [^"] correspond à: une citation, tout sauf une citation, une citation.
  2. Regex [^"]* correspond à: une citation, tout sauf une citation 0 (ou plus) fois, une citation.
  3. Cette expression rationnelle doit commencer par "gagner", sinon la correspondance tout sauf une virgule une ou plusieurs fois - c'est-à-dire: [^,]+ - "gagnerait".
  4. results() nécessite Java 9 ou supérieur.
  5. Il renvoie Stream<MatchResult>, que je mappe en utilisant l'appel group() et le collecte dans un tableau de chaînes. Un appel sans paramètre toArray() renverrait Object[].

S'il vous plaît voir l'extrait de code ci-dessous. Ce code ne considère que le flux heureux. Changer le en fonction de vos besoins

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
Abhijith Nagarajan