web-dev-qa-db-fra.com

Fractionner une chaîne qui a des espaces blancs, à moins qu'ils ne soient entre "guillemets"?

Pour simplifier les choses:

string streamR = sr.ReadLine();  // sr.Readline results in one "two two"

Je veux pouvoir les enregistrer sous deux chaînes différentes, supprimer tous les espaces SAUF pour les espaces trouvés entre guillemets. Par conséquent, ce dont j'ai besoin, c'est:

string 1 = one
string 2 = two two

Jusqu'ici, ce que j'ai trouvé qui fonctionne est le code suivant, mais il supprime les espaces entre les guillemets.

//streamR.ReadLine only has two strings
  string[] splitter = streamR.Split(' ');
    str1 = splitter[0];
    // Only set str2 if the length is >1
    str2 = splitter.Length > 1 ? splitter[1] : string.Empty;

La sortie de cela devient

one
two

J'ai cherché dans Expression rationnelle pour diviser des espaces sauf dans les guillemets mais je n'arrive pas à obtenir une expression régulière pour travailler/comprendre le code, en particulier pour savoir comment les scinder en deux chaînes différentes. Tous les codes me donnent une erreur de compilation (j'utilise System.Text.RegularExpressions)

44
Teachme
string input = "one \"two two\" three \"four four\" five six";
var parts = Regex.Matches(input, @"[\""].+?[\""]|[^ ]+")
                .Cast<Match>()
                .Select(m => m.Value)
                .ToList();
48
I4V

Vous pouvez même le faire sans Regex: une expression LINQ avec String.Split peut faire l'affaire.

Vous pouvez diviser votre chaîne auparavant par ", puis uniquement les éléments ayant un index égal dans le tableau résultant, par .

var result = myString.Split('"')
                     .Select((element, index) => index % 2 == 0  // If even index
                                           ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                           : new string[] { element })  // Keep the entire item
                     .SelectMany(element => element).ToList();

Pour la ficelle: 

This is a test for "Splitting a string" that has white spaces, unless they are "enclosed within quotes"

Cela donne le résultat:

This
is
a
test
for
Splitting a string
that
has
white
spaces,
unless
they
are
enclosed within quotes

METTRE À JOUR

string myString = "WordOne \"Word Two\"";
var result = myString.Split('"')
                     .Select((element, index) => index % 2 == 0  // If even index
                                           ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                           : new string[] { element })  // Keep the entire item
                     .SelectMany(element => element).ToList();

Console.WriteLine(result[0]);
Console.WriteLine(result[1]);
Console.ReadKey();

MISE À JOUR 2

Comment définissez-vous une partie citée de la chaîne?

Nous supposerons que la chaîne précédant le premier " n'est pas entre guillemets.

Ensuite, la chaîne placée entre le premier " et avant le second " est entre guillemets. La chaîne entre le deuxième " et le troisième " n'est pas entre guillemets. La chaîne entre la troisième et la quatrième est citée, ...

La règle générale est la suivante: Chaque chaîne entre le (2 * n-1) ème (nombre impair) " et le (2 * n) ème (nombre pair) " est entre guillemets. (1)

Quelle est la relation avec String.Split?

String.Split avec la valeur par défaut StringSplitOption (définie en tant que StringSplitOption.None) crée une liste de 1 chaîne, puis ajoute une nouvelle chaîne dans la liste pour chaque caractère de fractionnement trouvé.

Ainsi, avant le premier ", la chaîne est à l'index 0 dans le tableau fractionné, entre le premier et le deuxième ", la chaîne est à l'index 1 dans le tableau, entre le troisième et le quatrième, index 2, ...

La règle générale est la suivante: La chaîne entre le nième et le (n + 1) ème " est à l'index n du tableau. (2)

Le (1) et le (2) donnés, nous pouvons en conclure que: Les portions citées sont à un index impair dans le tableau fractionné.

28
Cédric Bignon

En tant qu'analyseur personnalisé, cela pourrait être plus approprié. 

C'est quelque chose que j'ai écrit une fois lorsque j'avais une exigence d'analyse particulière (et très étrange) qui impliquait des parenthèses et des espaces, mais elle est suffisamment générique pour pouvoir fonctionner avec pratiquement tous les délimiteurs et qualificateurs de texte.

public static IEnumerable<String> ParseText(String line, Char delimiter, Char textQualifier)
{

    if (line == null)
        yield break;

    else
    {
        Char prevChar = '\0';
        Char nextChar = '\0';
        Char currentChar = '\0';

        Boolean inString = false;

        StringBuilder token = new StringBuilder();

        for (int i = 0; i < line.Length; i++)
        {
            currentChar = line[i];

            if (i > 0)
                prevChar = line[i - 1];
            else
                prevChar = '\0';

            if (i + 1 < line.Length)
                nextChar = line[i + 1];
            else
                nextChar = '\0';

            if (currentChar == textQualifier && (prevChar == '\0' || prevChar == delimiter) && !inString)
            {
                inString = true;
                continue;
            }

            if (currentChar == textQualifier && (nextChar == '\0' || nextChar == delimiter) && inString)
            {
                inString = false;
                continue;
            }

            if (currentChar == delimiter && !inString)
            {
                yield return token.ToString();
                token = token.Remove(0, token.Length);
                continue;
            }

            token = token.Append(currentChar);

        }

        yield return token.ToString();

    } 
}

L'utilisation serait:

var parsedText = ParseText(streamR, ' ', '"');
9
psubsee2003

Vous pouvez utiliser la classe TextFieldParser qui fait partie de l'espace de noms Microsoft.VisualBasic.FileIO. (Vous devrez ajouter une référence à Microsoft.VisualBasic à votre projet.):

string inputString = "This is \"a test\" of the parser.";

using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(inputString)))
{
    using (Microsoft.VisualBasic.FileIO.TextFieldParser tfp = new TextFieldParser(ms))
    {
        tfp.Delimiters = new string[] { " " };
        tfp.HasFieldsEnclosedInQuotes = true;
        string[] output = tfp.ReadFields();

        for (int i = 0; i < output.Length; i++)
        {
            Console.WriteLine("{0}:{1}", i, output[i]);
        }
    }
}

Qui génère la sortie:

0:This
1:is
2:a test
3:of
4:the
5:parser.
9
John Koerner

Il y a juste un petit problème avec la réponse de Squazz .. cela fonctionne pour sa chaîne, mais pas si vous ajoutez plus d'éléments. Par exemple. 

string myString = "WordOne \"Word Two\" Three"

Dans ce cas, la suppression du dernier guillemet nous donnerait 4 résultats, pas trois. 

C’est facile à régler cependant .. comptez simplement le nombre de caractères d’échappement, et si le nombre de caractères est irrégulier, supprimez le dernier (adaptez-le à vos besoins)

    public static List<String> Split(this string myString, char separator, char escapeCharacter)
    {
        int nbEscapeCharactoers = myString.Count(c => c == escapeCharacter);
        if (nbEscapeCharactoers % 2 != 0) // uneven number of escape characters
        {
            int lastIndex = myString.LastIndexOf("" + escapeCharacter, StringComparison.Ordinal);
            myString = myString.Remove(lastIndex, 1); // remove the last escape character
        }
        var result = myString.Split(escapeCharacter)
                             .Select((element, index) => index % 2 == 0  // If even index
                                                   ? element.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                                   : new string[] { element })  // Keep the entire item
                             .SelectMany(element => element).ToList();
        return result;
    }

Je l'ai également transformé en une méthode d'extension et rendu séparateur et caractère d'échappement configurable.

1
user3566056

OP voulait 

... supprime tous les espaces SAUF pour les espaces trouvésentreguillemets

La solution de Cédric Bignon a presque fait cela, mais n'a pas tenu compte du fait qu'il pourrait y avoir un nombre impair de guillemets. Commencer par vérifier cela, puis éliminer les excès, garantit que nous ne cesserons de scinder que si l'élément est réellement encapsulé par des guillemets.

string myString = "WordOne \"Word Two";
int placement = myString.LastIndexOf("\"", StringComparison.Ordinal);
if (placement >= 0)
myString = myString.Remove(placement, 1);

var result = myString.Split('"')
                     .Select((element, index) => index % 2 == 0  // If even index
                                           ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                           : new string[] { element })  // Keep the entire item
                     .SelectMany(element => element).ToList();

Console.WriteLine(result[0]);
Console.WriteLine(result[1]);
Console.ReadKey();

Le mérite de la logique revient à Cédric Bignon, je n’ai ajouté qu’une sauvegarde.

0
Squazz

Avec le soutien des guillemets doubles.

Chaîne:

a "b b" "c ""c"" c"

Résultat:

a 
"b b"
"c ""c"" c"

Code:

var list=Regex.Matches(value, @"\""(\""\""|[^\""])+\""|[^ ]+", 
    RegexOptions.ExplicitCapture)
            .Cast<Match>()
            .Select(m => m.Value)
            .ToList();

Facultatif supprimer les guillemets doubles:

Select(m => m.StartsWith("\"") ? m.Substring(1, m.Length - 2).Replace("\"\"", "\"") : m)

Résultat

a 
b b
c "c" c
0
kux