Regex pour diviser une chaîne en utilisant un espace lorsqu'elle n'est pas entourée de guillemets simples ou doubles


114

Je suis nouveau dans les expressions régulières et j'apprécierais votre aide. J'essaie de créer une expression qui divisera la chaîne d'exemple en utilisant tous les espaces qui ne sont pas entourés de guillemets simples ou doubles. Ma dernière tentative ressemble à ceci: (?!")et ne fonctionne pas tout à fait. Il se partage sur l'espace avant le devis.

Exemple d'entrée:

This is a string that "will be" highlighted when your 'regular expression' matches something.

Sortie désirée:

This
is
a
string
that
will be
highlighted
when
your
regular expression
matches
something.

Notez cela "will be"et 'regular expression'conservez l'espace entre les mots.


Utilisez-vous réellement la méthode "split" ou est-ce qu'une boucle avec la méthode "find" sur Matcher serait suffisante?
erickson

9
"et maintenant il a deux problèmes"

Réponses:


251

Je ne comprends pas pourquoi tous les autres proposent des expressions régulières aussi complexes ou un code aussi long. Essentiellement, vous voulez récupérer deux types de choses de votre chaîne: des séquences de caractères qui ne sont pas des espaces ou des guillemets, et des séquences de caractères qui commencent et se terminent par des guillemets, sans guillemets entre les deux, pour deux types de guillemets. Vous pouvez facilement faire correspondre ces choses avec cette expression régulière:

[^\s"']+|"([^"]*)"|'([^']*)'

J'ai ajouté les groupes de capture parce que vous ne voulez pas les citations dans la liste.

Ce code Java construit la liste, en ajoutant le groupe de capture s'il correspond pour exclure les guillemets, et en ajoutant la correspondance globale de regex si le groupe de capture ne correspond pas (un mot sans guillemets correspond).

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    if (regexMatcher.group(1) != null) {
        // Add double-quoted string without the quotes
        matchList.add(regexMatcher.group(1));
    } else if (regexMatcher.group(2) != null) {
        // Add single-quoted string without the quotes
        matchList.add(regexMatcher.group(2));
    } else {
        // Add unquoted word
        matchList.add(regexMatcher.group());
    }
} 

Si cela ne vous dérange pas d'avoir les guillemets dans la liste renvoyée, vous pouvez utiliser un code beaucoup plus simple:

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    matchList.add(regexMatcher.group());
} 

1
Jan, merci pour votre réponse. BTW, je suis un grand fan d'EditPad.
carlsz

Que faire si je souhaite autoriser les guillemets échappés dans les chaînes \"?
Monstieur

3
Le problème avec cette réponse est avec une citation inégalée: les John's motherrésultats sont divisés en[John, s, mother]
leonbloy

2
Pour fixer les contours de leonbloy de problème, vous pouvez réorganiser les opérandes un peu et omettre les citations du groupe des espaces: "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper

1
Se fondant sur cela et d' autres réponses, l'expression régulière suivante permet l' échappement des caractères à l' intérieur des guillemets: "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. Voir stackoverflow.com/questions/5695240/…
Limnic

15

Il existe plusieurs questions sur StackOverflow qui couvrent cette même question dans divers contextes à l'aide d'expressions régulières. Par exemple:

MISE À JOUR : Exemple de regex pour gérer les chaînes entre guillemets simples et doubles. Réf: Comment puis-je diviser sur une chaîne sauf entre guillemets?

m/('.*?'|".*?"|\S+)/g 

J'ai testé cela avec un extrait de code Perl rapide et la sortie était comme reproduite ci-dessous. Fonctionne également pour les chaînes vides ou les chaînes d'espaces uniquement si elles sont entre guillemets (je ne sais pas si c'est souhaité ou non).

This
is
a
string
that
"will be"
highlighted
when
your
'regular expression'
matches
something.

Notez que cela inclut les caractères de guillemet eux-mêmes dans les valeurs correspondantes, bien que vous puissiez les supprimer avec une chaîne de remplacement ou modifier l'expression régulière pour ne pas les inclure. Je vais laisser cela comme un exercice pour le lecteur ou une autre affiche pour le moment, car 2h du matin est bien trop tard pour jouer avec les expressions régulières;)


Je pense que votre expression régulière autorise les citations incompatibles, par exemple "sera" et "expressions régulières".
Zach Scrivena

@Zach - vous avez raison, il le fait ... mis à jour pour résoudre ce problème au cas où
Jay


3

Le regex de Jan Goyvaerts est la meilleure solution que j'ai trouvée jusqu'à présent, mais crée également des correspondances vides (nulles), qu'il exclut dans son programme. Ces correspondances vides apparaissent également à partir des testeurs de regex (par exemple rubular.com). Si vous inversez les recherches (recherchez d'abord les parties citées et les mots séparés par des espaces), vous pouvez le faire une fois avec:

("[^"]*"|'[^']*'|[\S]+)+

2
(?<!\G".{0,99999})\s|(?<=\G".{0,99999}")\s

Cela correspondra aux espaces non entourés de guillemets doubles. Je dois utiliser min, max {0,99999} car Java ne prend pas en charge * et + dans lookbehind.


1

Il sera probablement plus facile de rechercher la chaîne, de saisir chaque partie, plutôt que de la diviser.

La raison étant, vous pouvez le diviser aux espaces avant et après "will be". Mais, je ne peux penser à aucun moyen de spécifier en ignorant l'espace entre à l'intérieur d'une scission.

(pas Java réel)

string = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";

regex = "\"(\\\"|(?!\\\").)+\"|[^ ]+"; // search for a quoted or non-spaced group
final = new Array();

while (string.length > 0) {
    string = string.trim();
    if (Regex(regex).test(string)) {
        final.push(Regex(regex).match(string)[0]);
        string = string.replace(regex, ""); // progress to next "word"
    }
}

En outre, la capture de guillemets simples peut entraîner des problèmes:

"Foo's Bar 'n Grill"

//=>

"Foo"
"s Bar "
"n"
"Grill"

Votre solution ne gère pas les chaînes entre guillemets simples, qui font partie de l'exemple de Carl.
Jan Goyvaerts

1

String.split()n'est pas utile ici car il n'y a aucun moyen de faire la distinction entre les espaces entre guillemets (ne pas diviser) et ceux à l'extérieur (diviser). Matcher.lookingAt()est probablement ce dont vous avez besoin:

String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
str = str + " "; // add trailing space
int len = str.length();
Matcher m = Pattern.compile("((\"[^\"]+?\")|('[^']+?')|([^\\s]+?))\\s++").matcher(str);

for (int i = 0; i < len; i++)
{
    m.region(i, len);

    if (m.lookingAt())
    {
        String s = m.group(1);

        if ((s.startsWith("\"") && s.endsWith("\"")) ||
            (s.startsWith("'") && s.endsWith("'")))
        {
            s = s.substring(1, s.length() - 1);
        }

        System.out.println(i + ": \"" + s + "\"");
        i += (m.group(0).length() - 1);
    }
}

qui produit la sortie suivante:

0: "This"
5: "is"
8: "a"
10: "string"
17: "that"
22: "will be"
32: "highlighted"
44: "when"
49: "your"
54: "regular expression"
75: "matches"
83: "something."

1

J'ai aimé l'approche de Marcus, cependant, je l'ai modifiée pour que je puisse autoriser le texte près des guillemets et prendre en charge les caractères "et 'quote". Par exemple, j'avais besoin d'un = "une valeur" pour ne pas le diviser en [a =, " une certaine valeur "].

(?<!\\G\\S{0,99999}[\"'].{0,99999})\\s|(?<=\\G\\S{0,99999}\".{0,99999}\"\\S{0,99999})\\s|(?<=\\G\\S{0,99999}'.{0,99999}'\\S{0,99999})\\s"

1

L'approche de Jan est excellente, mais en voici une autre pour mémoire.

Si vous vouliez réellement diviser comme mentionné dans le titre, en gardant les guillemets entre "will be"et 'regular expression', alors vous pouvez utiliser cette méthode qui est tout droit sortie de Match (ou remplacer) un modèle sauf dans les situations s1, s2, s3 etc.

Le regex:

'[^']*'|\"[^\"]*\"|( )

Les deux alternances de gauche correspondent complètes 'quoted strings'et "double-quoted strings". Nous ignorerons ces correspondances. Le côté droit correspond et capture les espaces du groupe 1, et nous savons que ce sont les bons espaces car ils ne correspondent pas aux expressions de gauche. Nous les remplaçons par SplitHerepuis divisés SplitHere. Encore une fois, c'est pour un vrai cas partagé où vous voulez "will be", non will be.

Voici une implémentation fonctionnelle complète (voir les résultats sur la démo en ligne ).

import java.util.*;
import java.io.*;
import java.util.regex.*;
import java.util.List;

class Program {
public static void main (String[] args) throws java.lang.Exception  {

String subject = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
Pattern regex = Pattern.compile("\'[^']*'|\"[^\"]*\"|( )");
Matcher m = regex.matcher(subject);
StringBuffer b= new StringBuffer();
while (m.find()) {
    if(m.group(1) != null) m.appendReplacement(b, "SplitHere");
    else m.appendReplacement(b, m.group(0));
}
m.appendTail(b);
String replaced = b.toString();
String[] splits = replaced.split("SplitHere");
for (String split : splits) System.out.println(split);
} // end main
} // end Program

1

Si vous utilisez c #, vous pouvez utiliser

string input= "This is a string that \"will be\" highlighted when your 'regular expression' matches <something random>";

List<string> list1 = 
                Regex.Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""|'(?<match>[\w\s]*)'|<(?<match>[\w\s]*)>").Cast<Match>().Select(m => m.Groups["match"].Value).ToList();

foreach(var v in list1)
   Console.WriteLine(v);

J'ai spécifiquement ajouté " | <(? [\ W \ s] *)> " pour souligner que vous pouvez spécifier n'importe quel caractère pour regrouper des phrases. (Dans ce cas, j'utilise <> pour grouper.

La sortie est:

This
is
a
string
that
will be
highlighted
when
your
regular expression 
matches
something random

0

Je suis raisonnablement certain que ce n'est pas possible en utilisant uniquement des expressions régulières. Vérifier si quelque chose est contenu dans une autre balise est une opération d'analyse. Cela semble être le même problème que d'essayer d'analyser XML avec une regex - cela ne peut pas être fait correctement. Vous pourrez peut-être obtenir le résultat souhaité en appliquant à plusieurs reprises une expression régulière non gourmande et non globale qui correspond aux chaînes entre guillemets, puis une fois que vous ne trouvez rien d'autre, divisez-la au niveau des espaces ... qui a un certain nombre de problèmes, y compris le suivi de l'ordre d'origine de toutes les sous-chaînes. Votre meilleur pari est d'écrire simplement une fonction très simple qui itère sur la chaîne et extrait les jetons que vous voulez.


C'est possible avec une regex, voir quelques-uns des exemples auxquels j'ai lié. Il y a quelques variantes à ce sujet, et j'ai vu plusieurs questions similaires sur SO qui abordent cela via des expressions régulières.
Jay

1
Savoir quand ne pas utiliser de regex est plus utile que de pouvoir créer un (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
René

0

Nous espérons que quelques ajustements utiles sur la réponse acceptée de Jan:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Autorise les guillemets échappés dans les chaînes entre guillemets
  • Évite de répéter le modèle pour les guillemets simples et doubles; cela simplifie également l'ajout de symboles entre guillemets si nécessaire (au détriment d'un groupe de capture supplémentaire)

Cela rompt les mots avec des apostrophes en eux, commeyou're
Design by Adrian

0

Vous pouvez également essayer ceci:

    String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something";
    String ss[] = str.split("\"|\'");
    for (int i = 0; i < ss.length; i++) {
        if ((i % 2) == 0) {//even
            String[] part1 = ss[i].split(" ");
            for (String pp1 : part1) {
                System.out.println("" + pp1);
            }
        } else {//odd
            System.out.println("" + ss[i]);
        }
    }

Vous devriez vraiment ajouter une explication sur la raison pour laquelle cela devrait fonctionner - vous pouvez également ajouter du code ainsi que les commentaires dans le code lui-même - dans sa forme actuelle, il ne fournit aucune explication qui peut aider le reste de la communauté à comprendre ce que vous l'avez fait pour résoudre / répondre à la question. Ceci est particulièrement important pour les questions qui ont déjà des réponses.
ishmaelMakitla

0

Ce qui suit renvoie un tableau d'arguments. Les arguments sont la variable «commande» divisée en espaces, sauf si elle est incluse entre guillemets simples ou doubles. Les correspondances sont ensuite modifiées pour supprimer les guillemets simples et doubles.

using System.Text.RegularExpressions;

var args = Regex.Matches(command, "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'").Cast<Match>
().Select(iMatch => iMatch.Value.Replace("\"", "").Replace("'", "")).ToArray();

2
Pouvez-vous ajouter un peu d'explication à votre réponse afin que les autres puissent la comprendre plus facilement? Idéalement, nous voulons éviter les réponses basées uniquement sur le code.
Jaquez

0

1er one-liner utilisant String.split ()

String s = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
String[] split = s.split( "(?<!(\"|').{0,255}) | (?!.*\\1.*)" );

[This, is, a, string, that, "will be", highlighted, when, your, 'regular expression', matches, something.]

ne pas diviser au blanc, si le blanc est entouré de guillemets simples ou doubles,
diviser le blanc lorsque les 255 caractères à gauche et tous les caractères à droite du blanc ne sont ni des guillemets simples ni des guillemets doubles

adapté de l'article original (ne gère que les guillemets doubles)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.