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
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
);
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
"[^"]*"
, nous correspondons à "double-quoted strings"
complet|
[^,]+
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.
Vous pouvez le faire très facilement sans expression régulière complexe:
"
. Vous obtenez une liste de cordesSi 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!
S'appuyant sur @ zx81 answer, parce que l'idée correspondante est vraiment agréable, j'ai ajouté Java 9results
call, 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
-> 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
String so = "123,test,444,\"don't split, this\",more test,1";
Pattern.compile("\"[^\"]*\"|[^,]+")
.matcher(so)
.results()
.map(MatchResult::group)
.toArray(String[]::new);
[^"]
correspond à: une citation, tout sauf une citation, une citation.[^"]*
correspond à: une citation, tout sauf une citation 0 (ou plus) fois, une citation.[^,]+
- "gagnerait".results()
nécessite Java 9 ou supérieur.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);
}