电子邮件地址拆分

10

我有一个字符串需要通过分号进行拆分。

电子邮件地址:"one@tw;,.'o"@hotmail.com;"some;thing"@example.com

这两个电子邮件地址都是有效的

我想得到以下List<string>列表:

  • "one@tw;,.'o"@hotmail.com
  • "some;thing"@example.com

但我当前使用的方式无法正确地拆分这些地址:

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

由于有多个 ; 字符,我最终得到了无效的电子邮件地址。

我试过几种不同的方法,甚至尝试判断字符串是否包含引号,然后找到 ; 字符的索引并以此解决,但这真的很麻烦。

请问有人有更好的建议吗?


1
我的建议是确保您的分隔符字符仅用于标记电子邮件之间的边界,而不会出现在其他任何地方,因此不应允许具有 ; 作为名称一部分的电子邮件(例如,“some;thing@example.com”)。否则,请查找其他分隔符字符,例如管道 | - code_dredd
1
请尝试以下正则表达式:(^|;)(.*?)@([\d\w]+[-]*)+\.\w+ - Camo
1
@ray 很不幸,我需要使用;字符,而它在电子邮件地址中是有效的。 - Jamie Rees
1
@ray 商业需求。 - Jamie Rees
1
@ray 这就是为什么@符号前面的部分要加引号。这样可以告诉编译器,分号不是作为分隔符使用的。 - juharr
显示剩余11条评论
3个回答

13

假设除了在“@”符号前的开头和结尾引号之外,不允许使用双引号,您可以使用以下正则表达式捕获电子邮件地址:

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

该想法是捕获符号 @ 前的未引用部分 [^@"]+ 或带引号的部分 "[^"]*",然后捕获直到分号 ; 或结束锚点 $ 的所有内容。

正则表达式示例。

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

这段代码会打印出:

"one@tw;,.'o"@hotmail.com
"some;thing"@example.com
hello@world

演示 1。

如果您想在双引号内允许转义的双引号,则可以使用更复杂的表达式:

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

其余内容保持不变。

Demo 2。


谢谢你的帮助。这个问题让我苦恼了好久! - Jamie Rees
如果允许使用双引号呢? - Jamie Rees
@JamieR 这取决于允许额外的双引号的规则。如果在引号字符串内允许额外的双引号,但是它们必须被转义,则正则表达式中的 "[^"]*" 部分会变得更加棘手,但仍可行。随处允许未限制的双引号会产生歧义。 - Sergey Kalinichenko
1
@JamieR 这里有一个演示,展示了如何在引号内或外使用转义引号的表达式。请注意,在代码中需要对这些引号进行反转义,因为它们会原样传输到输出中。 - Sergey Kalinichenko

4

显然,我开始编写反正则表达式方法的时间与 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 = "blah@blah.com;\"one@tw;,.'o\"@hotmail.com;\"some;thing\"@example.com;hello@world;asdasd@asd.co.uk;";
            foreach (var email in SplitEmailsByDelimiter(input, ';'))
            {
                Console.WriteLine(email);
            }

会给出这个输出
blah@blah.com
"one@tw;,.'o"@hotmail.com
"some;thing"@example.com
hello@world
asdasd@asd.co.uk

3
你也可以不使用正则表达式来实现这个功能。下面的扩展方法允许你指定分隔符和一个开始和结束转义序列的字符。请注意,它不验证所有的转义序列是否都被关闭。
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 = "\"one@tw;,.'o\"@hotmail.com;\"some;thing\"@example.com;hello@world;\"D;D@blah;blah.com\"";
foreach (var address in input.SpecialSplit(';', '"')) 
    Console.WriteLine(v);

给出以下输出结果:

"one@tw;,.'o"@hotmail.com

"some;thing"@example.com

hello@world

"D;D@blah;blah.com"

这是一个使用额外单个转义字符的版本。它假定两个连续的转义字符应该变成一个单一的转义字符,并且同时转义了beginEndEscape字符,以便不会触发转义序列的开始或结束,并转义了delimiter。在转义字符后面的任何其他内容都将被保留,并且转义字符将被删除。

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 检查,并注意如果传入空字符串,两者都将返回一个空字符串序列。


如果在 " 内部有另一个 ",例如:"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com,那该怎么办? - Jamie Rees
在这种情况下,您还需要告诉它双引号有一个转义字符。此外,您还需要考虑哪些内容可以和不能被转义。假设 "\" 将给您一个单个反斜杠,但是 "\t" 呢?您想要一个制表符还是只是一个单独的 t? - juharr
3
我建议不要再使用string.Substring方法,而是使用StringBuilder在循环遍历数据时添加字符。 - juharr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接