web-dev-qa-db-fra.com

Regex pour scinder une chaîne en utilisant un espace quand il n'est pas entouré de guillemets simples ou doubles

Je suis nouveau dans les expressions régulières et j'apprécierais votre aide. J'essaie de créer une expression qui divise l'exemple de chaîne 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. C'est fractionner l'espace avant la citation.

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 que "will be" et 'regular expression' conservent l’espace entre les mots.

97
carlsz

Je ne comprends pas pourquoi tous les autres proposent des expressions régulières aussi complexes ou un code aussi long. Vous souhaitez essentiellement extraire deux types d'éléments de votre chaîne: des séquences de caractères qui ne sont ni des espaces ni des guillemets, et des séquences de caractères qui commencent et se terminent par un guillemet, sans guillemets, pour deux types de guillemets. Vous pouvez facilement associer ces éléments à 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 générale des expressions rationnelles si le groupe de capture ne correspond pas (un mot non mis en correspondance était trouvé).

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());
} 
219
Jan Goyvaerts

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

UPDATE: Exemple de regex pour gérer les chaînes entre guillemets simples et doubles. Ref: Comment puis-je diviser une chaîne sauf entre guillemets?

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

Testé cela avec un extrait rapide de Perl et la sortie était reproduite ci-dessous. Fonctionne également pour les chaînes vides ou les chaînes ne contenant que des espaces si elles sont entre guillemets (vous ne savez pas si cela est souhaité ou non). 

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

Notez que cela inclut les guillemets eux-mêmes dans les valeurs correspondantes, bien que vous puissiez les supprimer avec une chaîne de remplacement ou modifier la expression rationnelle pour ne pas les inclure. Je laisserai 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;)

12
Jay

Si vous souhaitez autoriser les guillemets échappés à l'intérieur de la chaîne, vous pouvez utiliser quelque chose comme ceci:

(?:(['"])(.*?)(?<!\\)(?>\\\\)*\1|([^\s]+))

Les chaînes entre guillemets seront du groupe 2, les mots simples non entre guillemets seront du groupe 3.

Vous pouvez l'essayer sur différentes chaînes ici: http://www.fileformat.info/tool/regex.htm ou http://gskinner.com/RegExr/

5
mcrumley

La regex de Jan Goyvaerts est la meilleure solution que j'ai trouvée jusqu'à présent, mais elle crée également des correspondances vides (nulles), qu'il exclut dans son programme. Ces correspondances vides apparaissent également à partir d'essais sur les expressions rationnelles (par exemple, rubular.com) . Si vous effectuez une rotation des 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]+)+
3
iRon
(?<!\G".{0,99999})\s|(?<=\G".{0,99999}")\s

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

2
Marcus Andromeda

Il sera probablement plus facile de rechercher la chaîne en saisissant chaque partie plutôt que de la scinder.

Raison d'être, vous pouvez l'avoir divisé aux espaces avant et après "will be". Mais, je ne vois aucun moyen de spécifier en ignorant l'espace entre les divisions.

(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"
1
Jonathan Lonowski

String.split() n'est pas utile ici car il n'y a aucun moyen de distinguer les espaces entre guillemets (ne pas scinder) et ceux qui se trouvent en dehors 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
Zach Scrivena

Quelques ajustements, espérons-le, utiles sur la réponse acceptée de Jan:

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

L'approche de Jan est excellente mais voici une autre pour l'enregistrement. 

Si vous voulez réellement scinder comme mentionné dans le titre, en conservant les guillemets dans "will be" et 'regular expression', vous pouvez utiliser cette méthode, qui est directement issue de correspond (ou remplace)) un modèle, sauf dans les situations s1, s2, s3, etc.

La regex: 

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

Les deux alternations à gauche correspondent aux 'quoted strings' et "double-quoted strings" complets. Nous allons ignorer ces matchs. Le côté droit correspond aux espaces du groupe 1 et les capture, et nous savons que ce sont les bons espaces, car ils ne correspondaient pas aux expressions de gauche. Nous remplaçons ceux par SplitHere puis nous nous séparons sur SplitHere. Encore une fois, il s’agit d’un vrai cas où vous voulez "will be", pas will be.

Voici une implémentation 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
zx81

J'ai aimé l'approche de Marcus, cependant, je l'ai modifiée pour pouvoir autoriser le texte à proximité des guillemets et prendre en charge les caractères "et". 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
Eric Woodruff

Ce qui suit retourne 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();
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 expression régulière - cela ne peut pas être fait correctement. Vous pourrez peut-être obtenir le résultat souhaité en appliquant à plusieurs reprises une expression rationnelle non gloutonne et non globale qui correspond aux chaînes citées, puis une fois que vous ne pourrez plus rien trouver, séparez-le en plusieurs espaces ... comportant un certain nombre de problèmes, y compris le suivi de l'ordre d'origine de toutes les chaînes. Votre meilleur choix consiste simplement à écrire une fonction très simple qui itère sur la chaîne et extrait les jetons souhaités.

0
rmeador

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 indiquer que vous pouvez spécifier n'importe quel caractère pour grouper les 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
Praveen Singh

Vous pouvez aussi 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]);
        }
    }
0
Rakesh Sosa