Распределение адресов электронной почты

Итак, у меня есть строка, которую мне нужно разделить на точку с запятой

Адрес электронной почты: "[email protected];,.'o"@hotmail.com;"some;thing"@example.com

Оба адреса электронной почты действительны

Итак, я хочу иметь List<string> из следующего:

  • "один @ТВт;" о." @Hotmail.com
  • "некоторые, вещь" @example.com

Но способ, которым я сейчас разбиваю адреса, не работает:

var addresses = emailAddressString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(x => x.Trim()).ToList();

Из-за нескольких символов ; у меня заканчиваются неверные адреса электронной почты.

Я пробовал несколько разных способов, даже если сработал, если строка содержит кавычки, а затем найдет индекс символов ; и будет работать так, но это настоящая боль.

Есть ли у кого-нибудь лучшие предложения?

Ответ 1

Я начал писать метод anti regex примерно в то же время, что и juharr (Другой ответ). Я думал, что, поскольку я уже написал это, я бы представил его.

    public static IEnumerable<string> SplitEmailsByDelimiter(string input, char delimiter)
    {
        var startIndex = 0;
        var delimiterIndex = 0;

        while (delimiterIndex >= 0)
        {
            delimiterIndex = input.IndexOf(';', startIndex);
            string substring = input;
            if (delimiterIndex > 0)
            {
                substring = input.Substring(0, delimiterIndex);
            }

            if (!substring.Contains("\"") || substring.IndexOf("\"") != substring.LastIndexOf("\""))
            {
                yield return substring;
                input = input.Substring(delimiterIndex + 1);
                startIndex = 0;
            }
            else
            {
                startIndex = delimiterIndex + 1;
            }
        }
    }

Тогда следующее

            var input = "[email protected];\"[email protected];,.'o\"@hotmail.com;\"some;thing\"@example.com;[email protected];[email protected];";
            foreach (var email in SplitEmailsByDelimiter(input, ';'))
            {
                Console.WriteLine(email);
            }

Дает этот вывод

[email protected]
"[email protected];,.'o"@hotmail.com
"some;thing"@example.com
[email protected]
[email protected]

Ответ 2

Предполагая, что двойные кавычки не разрешены, кроме кавычек открытия и закрытия перед знаком "at" @, вы можете использовать это регулярное выражение для захвата адресов электронной почты:

((?:[^@"]+|"[^"]*")@[^;]+)(?:;|$)

Идея состоит в том, чтобы зафиксировать либо некотируемую [^@"]+, либо цитированную часть "[^"]*" до @, а затем захватить все до точки с запятой ; или конечную привязку $.

Демонстрация регулярного выражения.

var input = "\"[email protected];,.'o\"@hotmail.com;\"some;thing\"@example.com;[email protected]";
var mm = Regex.Matches(input, "((?:[^@\"]+|\"[^\"]*\")@[^;]+)(?:;|$)");
foreach (Match m in mm) {
    Console.WriteLine(m.Groups[1].Value);
}

Этот код печатает

"[email protected];,.'o"@hotmail.com
"some;thing"@example.com
[email protected]

Демо 1.

Если вы хотите разрешить скрытые двойные кавычки внутри двойных кавычек, вы можете использовать более сложное выражение:

((?:(?:[^@\"]|(?<=\\)\")+|\"([^\"]|(?<=\\)\")*\")@[^;]+)(?:;|$)

Все остальное остается прежним.

Демо 2.

Ответ 3

Вы также можете сделать это, не используя регулярные выражения. Следующий метод расширения позволит вам указать символ разделителя и символ, чтобы начинать и заканчивать escape-последовательности. Обратите внимание, что он не подтверждает, что все escape-последовательности закрыты.

public static IEnumerable<string> SpecialSplit(
    this string str, char delimiter, char beginEndEscape)
{
    int beginIndex = 0;
    int length = 0;
    bool escaped = false;
    foreach (char c in str)
    {
        if (c == beginEndEscape)
        {
            escaped = !escaped;
        }

        if (!escaped && c == delimiter)
        {
            yield return str.Substring(beginIndex, length);
            beginIndex += length + 1;
            length = 0;
            continue;
        }

        length++;
    }

    yield return str.Substring(beginIndex, length);
}

Тогда следующее

var input = "\"[email protected];,.'o\"@hotmail.com;\"some;thing\"@example.com;[email protected];\"D;[email protected];blah.com\"";
foreach (var address in input.SpecialSplit(';', '"')) 
    Console.WriteLine(v);

Пока давайте этот выход

"один @ТВт;., 'О" @hotmail.com

"некоторые, вещь" @example.com

привет @мир

"D; D @л, blah.com"

Здесь версия, которая работает с дополнительным одиночным escape-символом. Он предполагает, что два последовательных escape-символа должны стать одним единственным escape-символом, и он ускользает от устава beginEndEscape, чтобы он не запускал начало или конец escape-последовательности, а также избегал delimiter. Все остальное, что появляется после escape-символа, будет оставлено так же, как и с удаленным символом.

public static IEnumerable<string> SpecialSplit(
    this string str, char delimiter, char beginEndEscape, char singleEscape)
{
    StringBuilder builder = new StringBuilder();
    bool escapedSequence = false;
    bool previousEscapeChar = false;
    foreach (char c in str)
    {
        if (c == singleEscape && !previousEscapeChar)
        {
            previousEscapeChar = true;
            continue;
        }

        if (c == beginEndEscape && !previousEscapeChar)
        {
            escapedSequence = !escapedSequence;
        }

        if (!escapedSequence && !previousEscapeChar && c == delimiter)
        {
            yield return builder.ToString();
            builder.Clear();
            continue;
        }

        builder.Append(c);
        previousEscapeChar = false;
    }

    yield return builder.ToString();
}

Наконец, вы, вероятно, должны добавить null проверку строки, которая передается, и обратите внимание, что оба возвращают последовательность с одной пустой строкой, если вы передадите пустую строку.