Méthode d'extension C # - Fractionnement de chaîne acceptant également un caractère d'échappement

StackOverflow https://stackoverflow.com/questions/634777

Question

J'aimerais écrire une méthode d'extension pour la classe .NET String. J'aimerais qu'il s'agisse d'une variante spéciale de la méthode Split, qui prend un caractère d'échappement pour empêcher le fractionnement de la chaîne lorsqu'un caractère d'échappement est utilisé avant le séparateur.

Quelle est la meilleure façon d’écrire cela? Je suis curieux de savoir quelle est la meilleure façon de l'aborder sans regex.
Quelque chose avec une signature comme ...

public static string[] Split(this string input, string separator, char escapeCharacter)
{
   // ...
}

MISE À JOUR: Parce qu’il est apparu dans l’un des commentaires, les échappés ...

En C # lors de l'échappement de caractères non spéciaux, vous obtenez l'erreur - CS1009: séquence d'échappement non reconnue.

Dans IE JScript, les caractères d'échappement sont supprimés. Si vous n'essayez pas et que vous obtenez un " chiffre hexadécimal attendu " Erreur. J'ai testé Firefox et il a le même comportement.

Je voudrais que cette méthode soit assez tolérante et suive le modèle JavaScript. Si vous vous échappez sur un non-séparateur, il devrait simplement "aimablement". supprimez le caractère d'échappement.

Était-ce utile?

La solution

Que diriez-vous de:

public static IEnumerable<string> Split(this string input, 
                                        string separator,
                                        char escapeCharacter)
{
    int startOfSegment = 0;
    int index = 0;
    while (index < input.Length)
    {
        index = input.IndexOf(separator, index);
        if (index > 0 && input[index-1] == escapeCharacter)
        {
            index += separator.Length;
            continue;
        }
        if (index == -1)
        {
            break;
        }
        yield return input.Substring(startOfSegment, index-startOfSegment);
        index += separator.Length;
        startOfSegment = index;
    }
    yield return input.Substring(startOfSegment);
}

Cela semble fonctionner (avec quelques chaînes de test rapides), mais cela ne supprime pas le caractère d'échappement - cela dépend de votre situation exacte, je suppose.

Autres conseils

Cela devra être un peu nettoyé, mais c’est essentiellement cela ....

List<string> output = new List<string>();
for(int i=0; i<input.length; ++i)
{
    if (input[i] == separator && (i==0 || input[i-1] != escapeChar))
    {
        output.Add(input.substring(j, i-j);
        j=i;
    }
}

return output.ToArray();

Ma première observation est que le séparateur doit être un caractère, pas une chaîne, car échapper une chaîne à l'aide d'un seul caractère peut être difficile. Quelle partie de la chaîne suivante le caractère d'échappement recouvre-t-il? En dehors de cela, la réponse de @James Curran est à peu près ce que je ferais - mais comme il dit que cela nécessite un nettoyage. Initialisation de j à 0 dans l'initialiseur de boucle, par exemple. Déterminer comment gérer les entrées nulles, etc.

Vous voudrez probablement également prendre en charge StringSplitOptions et spécifier si une chaîne vide doit être renvoyée dans la collection.

Voici la solution si vous souhaitez supprimer le caractère d'échappement.

public static IEnumerable<string> Split(this string input, 
                                        string separator, 
                                        char escapeCharacter) {
    string[] splitted = input.Split(new[] { separator });
    StringBuilder sb = null;

    foreach (string subString in splitted) {
        if (subString.EndsWith(escapeCharacter.ToString())) {
            if (sb == null)
                sb = new StringBuilder();
            sb.Append(subString, 0, subString.Length - 1);
        } else {
            if (sb == null)
                yield return subString;
            else {
                sb.Append(subString);
                yield return sb.ToString();
                sb = null;
            }
        }
    }
    if (sb != null)
        yield return sb.ToString();
}

Vous pouvez essayer quelque chose comme ça. Bien que, je suggère de mettre en œuvre avec un code non sécurisé pour les tâches critiques de performance.

public static class StringExtensions
{
    public static string[] Split(this string text, char escapeChar, params char[] seperator)
    {
        return Split(text, escapeChar, seperator, int.MaxValue, StringSplitOptions.None);
    }

    public static string[] Split(this string text, char escapeChar, char[] seperator, int count)
    {
        return Split(text, escapeChar, seperator, count, StringSplitOptions.None);
    }

    public static string[] Split(this string text, char escapeChar, char[] seperator, StringSplitOptions options)
    {
        return Split(text, escapeChar, seperator, int.MaxValue, options);
    }

    public static string[] Split(this string text, char escapeChar, char[] seperator, int count, StringSplitOptions options)
    {
        if (text == null)
        {
            throw new ArgumentNullException("text");
        }

        if (text.Length == 0)
        {
            return new string[0];
        }

        var segments = new List<string>();

        bool previousCharIsEscape = false;
        var segment = new StringBuilder();

        for (int i = 0; i < text.Length; i++)
        {
            if (previousCharIsEscape)
            {
                previousCharIsEscape = false;

                if (seperator.Contains(text[i]))
                {
                    // Drop the escape character when it escapes a seperator character.
                    segment.Append(text[i]);
                    continue;
                }

                // Retain the escape character when it escapes any other character.
                segment.Append(escapeChar);
                segment.Append(text[i]);
                continue;
            }

            if (text[i] == escapeChar)
            {
                previousCharIsEscape = true;
                continue;
            }

            if (seperator.Contains(text[i]))
            {
                if (options != StringSplitOptions.RemoveEmptyEntries || segment.Length != 0)
                {
                    // Only add empty segments when options allow.
                    segments.Add(segment.ToString());
                }

                segment = new StringBuilder();
                continue;
            }

            segment.Append(text[i]);
        }

        if (options != StringSplitOptions.RemoveEmptyEntries || segment.Length != 0)
        {
            // Only add empty segments when options allow.
            segments.Add(segment.ToString());
        }

        return segments.ToArray();
    }
}
public static string[] Split(this string input, string separator, char escapeCharacter)
{
    Guid g = Guid.NewGuid();
    input = input.Replace(escapeCharacter.ToString() + separator, g.ToString());
    string[] result = input.Split(new string []{separator}, StringSplitOptions.None);
    for (int i = 0; i < result.Length; i++)
    {
        result[i] = result[i].Replace(g.ToString(), escapeCharacter.ToString() + separator);
    }

    return result;
}

Ce n’est probablement pas la meilleure façon de le faire, mais c’est une autre alternative. En gros, partout où la séquence d'échappement + séparateur est trouvée, remplacez-la par un GUID (vous pouvez utiliser n'importe quelle autre merde aléatoire ici, peu importe). Ensuite, utilisez la fonction split intégrée. Puis remplacez le GUID dans chaque élément du tableau par le caractère d'échappement + séparateur.

La signature est incorrecte, vous devez renvoyer un tableau de chaînes AVERTISSEMENT EXTENSIONS JAMAIS UTILISÉES, alors pardonnez-moi certaines erreurs;)

public static List<String> Split(this string input, string separator, char escapeCharacter)
{
    String word = "";
    List<String> result = new List<string>();
    for (int i = 0; i < input.Length; i++)
    {
//can also use switch
        if (input[i] == escapeCharacter)
        {
            break;
        }
        else if (input[i] == separator)
        {
            result.Add(word);
            word = "";
        }
        else
        {
            word += input[i];    
        }
    }
    return result;
}

Personnellement, je tricherais et jetterais un œil à string.Split en utilisant un réflecteur ... InternalSplitOmitEmptyEntries semble utile; -)

J'ai eu ce problème aussi et je n'ai pas trouvé de solution. J'ai donc écrit moi-même une telle méthode:

    public static IEnumerable<string> Split(
        this string text, 
        char separator, 
        char escapeCharacter)
    {
        var builder = new StringBuilder(text.Length);

        bool escaped = false;
        foreach (var ch in text)
        {
            if (separator == ch && !escaped)
            {
                yield return builder.ToString();
                builder.Clear();
            }
            else
            {
                // separator is removed, escape characters are kept
                builder.Append(ch);
            }
            // set escaped for next cycle, 
            // or reset unless escape character is escaped.
            escaped = escapeCharacter == ch && !escaped;
        }
        yield return builder.ToString();
    }

Il est associé à Escape et Unescape, qui échappe au séparateur et au caractère d'échappement et supprime à nouveau les caractères d'échappement:

    public static string Escape(this string text, string controlChars, char escapeCharacter)
    {
        var builder = new StringBuilder(text.Length + 3);
        foreach (var ch in text)
        {
            if (controlChars.Contains(ch))
            {
                builder.Append(escapeCharacter);
            }
            builder.Append(ch);
        }
        return builder.ToString();
    }

    public static string Unescape(string text, char escapeCharacter)
    {
        var builder = new StringBuilder(text.Length);
        bool escaped = false;
        foreach (var ch in text)
        {
            escaped = escapeCharacter == ch && !escaped;
            if (!escaped)
            {
                builder.Append(ch);
            }
        }
        return builder.ToString();
    }

Exemples pour escape / unescape

separator = ','
escapeCharacter = '\\'
//controlCharacters is always separator + escapeCharacter

@"AB,CD\EF\," <=> @"AB\,CD\\EF\\\,"

Diviser:

@"AB,CD\,EF\\,GH\\\,IJ" => [@"AB", @"CD\,EF\\", @"GH\\\,IJ"]

Donc, pour l'utiliser, Echap avant de rejoindre et Unescape après la scission.

public string RemoveMultipleDelimiters(string sSingleLine)
{
    string sMultipleDelimitersLine = "";
    string sMultipleDelimitersLine1 = "";
    int iDelimeterPosition = -1;
    iDelimeterPosition = sSingleLine.IndexOf('>');
    iDelimeterPosition = sSingleLine.IndexOf('>', iDelimeterPosition + 1);
    if (iDelimeterPosition > -1)
    {
        sMultipleDelimitersLine = sSingleLine.Substring(0, iDelimeterPosition - 1);
        sMultipleDelimitersLine1 = sSingleLine.Substring(sSingleLine.IndexOf('>', iDelimeterPosition) - 1);
        sMultipleDelimitersLine1 = sMultipleDelimitersLine1.Replace('>', '*');
        sSingleLine = sMultipleDelimitersLine + sMultipleDelimitersLine1;
    }
    return sSingleLine;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top