web-dev-qa-db-fra.com

Expression régulière à diviser sur des espaces, sauf entre guillemets

Je voudrais utiliser la méthode. Net Regex.Split pour scinder cette chaîne d'entrée en un tableau. Il doit diviser sur des espaces sauf s'il est entouré d'un devis.

Entrée: Voici "ma chaîne" il a "six correspondances"

Production attendue:

  1. Ici
  2. est
  3. ma ficelle
  4. il
  5. a
  6. six matches

De quel motif ai-je besoin? De plus, dois-je spécifier des options Regex?

65
Shaun Bowe

Aucune option requise

Regex:

\w+|"[\w\s]*"

C #:

Regex regex = new Regex(@"\w+|""[\w\s]*""");

Ou si vous devez exclure des "caractères:

    Regex
        .Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""")
        .Cast<Match>()
        .Select(m => m.Groups["match"].Value)
        .ToList()
        .ForEach(s => Console.WriteLine(s));
62
Bartek Szabat

La solution de Lieven fait l’objet d’une grande partie du chemin et, comme il le dit dans ses commentaires, il s’agit simplement de changer la fin en solution de Bartek. Le résultat final est le regEx de travail suivant:

(?<=")\w[\w\s]*(?=")|\w+|"[\w\s]*"

Entrée: Voici "ma chaîne" il a "six correspondances"

Sortie:

  1. Ici
  2. est
  3. "ma ficelle"
  4. il
  5. a
  6. "six allumettes"

Malheureusement, cela inclut les citations. Si vous utilisez plutôt les éléments suivants:

(("((?<token>.*?)(?<!\\)")|(?<token>[\w]+))(\s)*)

Et capturez explicitement le "jeton" correspond comme suit:

    RegexOptions options = RegexOptions.None;
    Regex regex = new Regex( @"((""((?<token>.*?)(?<!\\)"")|(?<token>[\w]+))(\s)*)", options );
    string input = @"   Here is ""my string"" it has   "" six  matches""   ";
    var result = (from Match m in regex.Matches( input ) 
                  where m.Groups[ "token" ].Success
                  select m.Groups[ "token" ].Value).ToList();

    for ( int i = 0; i < result.Count(); i++ )
    {
        Debug.WriteLine( string.Format( "Token[{0}]: '{1}'", i, result[ i ] ) );
    }

Sortie de débogage:

Token[0]: 'Here'
Token[1]: 'is'
Token[2]: 'my string'
Token[3]: 'it'
Token[4]: 'has'
Token[5]: ' six  matches'
16
Timothy Walters

La meilleure réponse ne fonctionne pas vraiment pour moi. J'essayais de diviser ce type de chaîne par des espaces, mais il semblerait que les points ('.') Se divisent également.

"the lib.lib" "another lib".lib

Je sais que la question concerne les regex, mais j'ai fini par écrire une fonction non regex pour le faire:

    /// <summary>
    /// Splits the string passed in by the delimiters passed in.
    /// Quoted sections are not split, and all tokens have whitespace
    /// trimmed from the start and end.
    public static List<string> split(string stringToSplit, params char[] delimiters)
    {
        List<string> results = new List<string>();

        bool inQuote = false;
        StringBuilder currentToken = new StringBuilder();
        for (int index = 0; index < stringToSplit.Length; ++index)
        {
            char currentCharacter = stringToSplit[index];
            if (currentCharacter == '"')
            {
                // When we see a ", we need to decide whether we are
                // at the start or send of a quoted section...
                inQuote = !inQuote;
            }
            else if (delimiters.Contains(currentCharacter) && inQuote == false)
            {
                // We've come to the end of a token, so we find the token,
                // trim it and add it to the collection of results...
                string result = currentToken.ToString().Trim();
                if (result != "") results.Add(result);

                // We start a new token...
                currentToken = new StringBuilder();
            }
            else
            {
                // We've got a 'normal' character, so we add it to
                // the curent token...
                currentToken.Append(currentCharacter);
            }
        }

        // We've come to the end of the string, so we add the last token...
        string lastResult = currentToken.ToString().Trim();
        if (lastResult != "") results.Add(lastResult);

        return results;
    }
8
Richard Shepherd

J'utilisais la réponse de Bartek Szabat, mais je devais capturer plus que des "\ w" caractères dans mes jetons. Pour résoudre le problème, j'ai légèrement modifié sa regex, semblable à la réponse de Grzenio:

Regular Expression: (?<match>[^\s"]+)|(?<match>"[^"]*")

C# String:          (?<match>[^\\s\"]+)|(?<match>\"[^\"]*\")

Le code de Bartek (qui renvoie des jetons dépourvus de guillemets) devient:

Regex
        .Matches(input, "(?<match>[^\\s\"]+)|(?<match>\"[^\"]*\")")
        .Cast<Match>()
        .Select(m => m.Groups["match"].Value)
        .ToList()
        .ForEach(s => Console.WriteLine(s));
7
Boinst

J'ai trouvé la regex dans cette réponse être très utile. Pour que cela fonctionne en C #, vous devrez utiliser la classe MatchCollection.

//need to escape \s
string pattern = "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'";

MatchCollection parsedStrings = Regex.Matches(line, pattern);

for (int i = 0; i < parsedStrings.Count; i++)
{
    //print parsed strings
    Console.Write(parsedStrings[i].Value + " ");
}
Console.WriteLine();
5
sttaq

Cette expression rationnelle sera divisée en fonction du cas que vous avez donné ci-dessus, bien qu'elle ne supprime pas les guillemets ni les espaces supplémentaires. Vous souhaiterez donc peut-être effectuer un post-traitement sur vos chaînes. Cela devrait cependant maintenir correctement les chaînes entre guillemets.

"[^"]+"|\s?\w+?\s
4
John Conrad

Avec un peu de désordre, les langages normaux peuvent garder une trace du comptage pair/impair des guillemets, mais si vos données peuvent inclure des guillemets sautés (\ "), vous rencontrez un réel problème pour produire ou comprendre une expression régulière qui gérera cela correctement .

2
Liudvikas Bukys

EDIT: Désolé pour mon post précédent, c'est évidemment possible.

Pour gérer tous les caractères non alphanumériques, vous avez besoin de quelque chose comme ceci:

MatchCollection matchCollection = Regex.Matches(input, @"(?<match>[^""\s]+)|\""(?<match>[^""]*)""");
foreach (Match match in matchCollection)
        {
            yield return match.Groups["match"].Value;
        }

vous pouvez rendre le foreach plus intelligent si vous utilisez .Net> 2.0

1
Grzenio

Shaun,

Je crois que la regex suivante devrait le faire

(?<=")\w[\w\s]*(?=")|\w+  

Cordialement,
Lieven

1
Lieven Keersmaekers

Si vous souhaitez jeter un œil à une solution générale à ce problème sous la forme d'un objet javascript gratuit et à code source ouvert, vous pouvez visiter http://splitterjsobj.sourceforge.net/ pour une démonstration en direct. (et télécharger). L'objet a les caractéristiques suivantes:

  • Des paires de caractères de guillemets définis par l'utilisateur peuvent être utilisées pour sortir du délimiteur (empêcher une séparation entre guillemets). Les guillemets peuvent être échappés avec un caractère d'échappement défini par l'utilisateur et/ou par "échappement de guillemets double". Le personnage de sortie peut être échappé (avec lui-même). Dans l’un des 5 tableaux de sortie (propriétés de l’objet), la sortie n’est pas échappée. (Par exemple, si le caractère d'échappement = /, "a ///" b "n'est pas échappé sous la forme d'un /" b)
  • Diviser sur un tableau de délimiteurs; analyser un fichier en un seul appel. (Les tableaux de sortie seront imbriqués.)
  • Toutes les séquences d'échappement reconnues par javascript peuvent être évaluées lors du processus de scission et/ou d'un prétraitement.
  • Fonctionnalité de rappel
  • Cohérence entre les navigateurs

L'objet est également disponible en tant que plug-in jQuery, mais en tant que nouvel utilisateur de ce site, je ne peux inclure qu'un seul lien dans ce message. 

0
Brian W

Jetez un coup d’œil à la fonction " Split de LSteinle qui prend en charge les qualificateurs de texte " dans le projet Code

Voici l'extrait de son projet qui vous intéresse.

using System.Text.RegularExpressions;

public string[] Split(string expression, string delimiter, string qualifier, bool ignoreCase)
{
    string _Statement = String.Format("{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))", 
                        Regex.Escape(delimiter), Regex.Escape(qualifier));

    RegexOptions _Options = RegexOptions.Compiled | RegexOptions.Multiline;
    if (ignoreCase) _Options = _Options | RegexOptions.IgnoreCase;

    Regex _Expression = New Regex(_Statement, _Options);
    return _Expression.Split(expression);
}

Veillez simplement à appeler ceci dans une boucle lors de la création et de la compilation de l'instruction Regex chaque fois que vous l'appelez. Donc, si vous avez besoin de l'appeler plus d'une poignée de fois, je créerais un cache Regex.

0
Adam L