Метод расширения C # - String Split, который также принимает символ Escape - PullRequest
7 голосов
/ 11 марта 2009

Я хотел бы написать метод расширения для класса .NET String. Я хотел бы, чтобы это был особый вариант метода Split - тот, который принимает escape-символ, чтобы предотвратить разбиение строки, когда escape-символ используется перед разделителем.

Какой лучший способ написать это? Мне любопытно, как лучше к нему не относиться к регулярным выражениям.
Нечто с подписью типа ...

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

UPDATE: Потому что он всплыл в одном из комментариев, спасаясь ...

В C # при экранировании не специальных символов вы получаете ошибку - CS1009: Нераспознанная escape-последовательность.

В IE JScript экранирующие символы выбрасываются. Если вы не попытаетесь \ u, а затем получите ошибку «Ожидаемая шестнадцатеричная цифра». Я тестировал Firefox, и у него такое же поведение.

Мне бы хотелось, чтобы этот метод был довольно прощающим и следовал модели JavaScript. Если вы выберете символ без разделителя, он должен просто «любезно» удалить символ экранирования.

Ответы [ 10 ]

12 голосов
/ 11 марта 2009

Как насчет:

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);
}

Это похоже на работу (с несколькими короткими тестовыми строками), но не удаляет escape-символ - я думаю, это будет зависеть от вашей конкретной ситуации.

7 голосов
/ 11 марта 2009

Это нужно будет немного убрать, но это по сути это ...

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();
4 голосов
/ 11 марта 2009

Вот решение, если вы хотите удалить escape-символ.

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();
}
4 голосов
/ 11 марта 2009

Мое первое наблюдение состоит в том, что разделитель должен быть символом, а не строкой, поскольку экранирование строки с использованием одного символа может быть трудным - сколько из следующей строки покрывает escape-символ? Кроме этого, ответ @James Curran - это почти то, как я бы с этим справился - хотя, по его словам, он нуждается в некоторой очистке. Например, инициализация от j до 0 в инициализаторе цикла. Выяснить, как обрабатывать нулевые входы и т. Д.

Возможно, вы также хотите поддержать StringSplitOptions и указать, должна ли пустая строка возвращаться в коллекции.

3 голосов
/ 22 августа 2013

Вы можете попробовать что-то вроде этого. Хотя я бы предложил использовать небезопасный код для задач, критичных для производительности.

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();
    }
}
3 голосов
/ 11 марта 2009
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;
}

Вероятно, не самый лучший способ сделать это, но это еще одна альтернатива. По сути, везде, где встречается последовательность escape + seperator, замените ее на GUID (здесь вы можете использовать любую другую случайную чушь, не имеет значения). Затем используйте встроенную функцию разделения. Затем замените guid в каждом элементе массива на escape + разделитель.

1 голос
/ 22 января 2015

У меня тоже была эта проблема, и я не нашел решения. Поэтому я сам написал такой метод:

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

Он сочетается с Escape и Unescape, которые экранируют разделитель и escape-символ и снова удаляют escape-символы:

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

Примеры для побега / unescape

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

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

Split:

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

Таким образом, чтобы использовать его, Escape до Join и Unescape после Split.

1 голос
/ 11 марта 2009

Лично я бы обманул и взглянул на строку. Сплит с помощью отражателя ... InternalSplitOmitEmptyEntries выглядит полезным; -)

1 голос
/ 11 марта 2009

Подпись неверна, вам нужно вернуть массив строк

WARNIG НИКОГДА НЕ ИСПОЛЬЗУЕТ РАСШИРЕНИЯ, так что простите меня за некоторые ошибки;)

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;
}
0 голосов
/ 28 июля 2009
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;
}
...