解析电子邮件地址字符串的最佳方法

13

我正在处理一些电子邮件头数据,对于收件人(to:)、发件人(from:)、抄送人(cc:)和密送人(bcc:)字段,电子邮件地址可以采用多种不同的方式表示:

First Last <name@domain.com>
Last, First <name@domain.com>
name@domain.com

这些变体可以以任何顺序出现在同一条消息中,全部出现在一个逗号分隔的字符串中:

First, Last <name@domain.com>, name@domain.com, First Last <name@domain.com>

我一直在尝试想出一种方法,将此字符串解析为每个人的单独名字、姓氏和电子邮件(如果仅提供电子邮件地址,则省略名称)。

有人能建议最好的方法吗?

我已经尝试了逗号分隔,这个方法本来可以工作,但在第二个示例中,姓氏被放在了首位。我想这种方法可能可行,如果我在拆分后检查每个元素并查看它是否包含“@”或 “<”/“>”,如果不包含,则可以假定下一个元素是名字。这是一种好的方法吗?还有其他格式的地址我忽略了吗?


更新:也许我应该稍微澄清一下,基本上我要做的就是将包含多个地址的字符串拆分成包含以任何格式发送的地址的单个字符串。我有自己的方法来验证和从地址中提取信息,只是对我来说难以找到最好的方法来分隔每个地址。

以下是我想出来的解决方案:

String str = "Last, First <name@domain.com>, name@domain.com, First Last <name@domain.com>, \"First Last\" <name@domain.com>";

List<string> addresses = new List<string>();
int atIdx = 0;
int commaIdx = 0;
int lastComma = 0;
for (int c = 0; c < str.Length; c++)
{
    if (str[c] == '@')
        atIdx = c;

    if (str[c] == ',')
        commaIdx = c;

    if (commaIdx > atIdx && atIdx > 0)
    {
        string temp = str.Substring(lastComma, commaIdx - lastComma);
        addresses.Add(temp);
        lastComma = commaIdx;
        atIdx = commaIdx;
    }

    if (c == str.Length -1)
    {
        string temp = str.Substring(lastComma, str.Legth - lastComma);
        addresses.Add(temp);
    }
}

if (commaIdx < 2)
{
    // if we get here we can assume either there was no comma, or there was only one comma as part of the last, first combo
    addresses.Add(str);
}

上述代码生成了各个地址,我可以在后续操作中进一步处理这些地址。


你能控制头部数据以何种方式传递给你吗? - Ian Jacobs
我使用了这个方法,非常有帮助,只想指出一个我必须进行的微调。在检查循环是否已经到达字符串结尾时,我必须将逗号索引设置为字符串长度,或者更具体地说,大于2。如果(commaIdx < 2)检查会在输入字符串是没有逗号的单个电子邮件地址时向List<>添加重复项。 - A. Wilson
可能是如何解析格式为“名称<电子邮件>”的字符串的重复问题。 - Michael Freidgeim
13个回答

0

// 基于Michael Perry的回答 * // 需要处理first.last@domain.com、first_last@domain.com和相关语法 // 同时在这些电子邮件语法中查找名字和姓氏

public class ParsedEmail
{
    private string _first;
    private string _last;
    private string _name;
    private string _domain;

    public ParsedEmail(string first, string last, string name, string domain)
    {
        _name = name;
        _domain = domain;

        // first.last@domain.com, first_last@domain.com etc. syntax
        char[] chars = { '.', '_', '+', '-' };
        var pos = _name.IndexOfAny(chars);

        if (string.IsNullOrWhiteSpace(_first) && string.IsNullOrWhiteSpace(_last) && pos > -1)
        {
            _first = _name.Substring(0, pos);
            _last = _name.Substring(pos+1);
        }
    }

    public string First
    {
        get { return _first; }
    }

    public string Last
    {
        get { return _last; }
    }

    public string Name
    {
        get { return _name; }
    }

    public string Domain
    {
        get { return _domain; }
    }

    public string Email
    {
        get
        {
            return Name + "@" + Domain;
        }
    }

    public override string ToString()
    {
        return Email;
    }

    public static IEnumerable<ParsedEmail> SplitEmailList(string delimList)
    {
        delimList = delimList.Replace("\"", string.Empty);

        Regex re = new Regex(
                    @"((?<last>\w*), (?<first>\w*) <(?<name>[a-zA-Z_0-9\.\+\-]+)@(?<domain>\w*\.\w*)>)|" +
                    @"((?<first>\w*) (?<last>\w*) <(?<name>[a-zA-Z_0-9\.\+\-]+)@(?<domain>\w*\.\w*)>)|" +
                    @"((?<name>[a-zA-Z_0-9\.\+\-]+)@(?<domain>\w*\.\w*))");


        MatchCollection matches = re.Matches(delimList);

        var parsedEmails =
                   (from Match match in matches
                    select new ParsedEmail(
                            match.Groups["first"].Value,
                            match.Groups["last"].Value,
                            match.Groups["name"].Value,
                            match.Groups["domain"].Value)).ToList();

        return parsedEmails;

    }


}

我发现Michael Perry的回答非常有帮助。对他的回答进行了一些调整,使其允许first.last@domain.com和相关语法,并在使用这些语法时直接从电子邮件中解析出人名。 - Vince

0

我决定在两个限制上划下界限:

  1. To和Cc头必须是csv可解析的字符串。
  2. 任何MailAddress无法解析的内容,我都不会担心它。

我还决定只对电子邮件地址感兴趣,而不是显示名称,因为显示名称很棘手且难以定义,而电子邮件地址可以进行验证。因此,我使用MailAddress来验证我的解析。

我像处理csv字符串一样处理To和Cc头,同样,任何无法以这种方式解析的内容我都不会担心它。

private string GetProperlyFormattedEmailString(string emailString)
    {
        var emailStringParts = CSVProcessor.GetFieldsFromString(emailString);

        string emailStringProcessed = "";

        foreach (var part in emailStringParts)
        {
            try
            {
                var address = new MailAddress(part);
                emailStringProcessed += address.Address + ",";
            }
            catch (Exception)
            {
                //wasn't an email address
                throw;
            }
        }

        return emailStringProcessed.TrimEnd((','));
    }

编辑

进一步的研究表明我的假设是正确的。阅读规范RFC 2822,基本上显示收件人(To)、抄送(Cc)和密送(Bcc)字段都是可解析的csv字段。所以,虽然像任何csv解析一样存在许多陷阱,但如果您有一种可靠的解析csv字段的方法(Microsoft.VisualBasic.FileIO命名空间中的TextFieldParser就是这样的方法,也是我用于此的方法),那么您就可以轻松应对。

编辑2

显然,它们不需要是有效的CSV字符串……引号会使事情变得混乱。因此,您的csv解析器必须具备容错能力。我让它尝试解析字符串,如果失败,则删除所有引号并再次尝试:

public static string[] GetFieldsFromString(string csvString)
    {
        using (var stringAsReader = new StringReader(csvString))
        {
            using (var textFieldParser = new TextFieldParser(stringAsReader))
            {
                SetUpTextFieldParser(textFieldParser, FieldType.Delimited, new[] {","}, false, true);

                try
                {
                    return textFieldParser.ReadFields();
                }
                catch (MalformedLineException ex1)
                {
                    //assume it's not parseable due to double quotes, so we strip them all out and take what we have
                    var sanitizedString = csvString.Replace("\"", "");

                    using (var sanitizedStringAsReader = new StringReader(sanitizedString))
                    {
                        using (var textFieldParser2 = new TextFieldParser(sanitizedStringAsReader))
                        {
                            SetUpTextFieldParser(textFieldParser2, FieldType.Delimited, new[] {","}, false, true);

                            try
                            {
                                return textFieldParser2.ReadFields().Select(part => part.Trim()).ToArray();
                            }
                            catch (MalformedLineException ex2)
                            {
                                return new string[] {csvString};
                            }
                        }
                    }
                }
            }
        }
    }

它无法处理电子邮件中的带引号的账户,例如"Monkey Header"@stupidemailaddresses.com。

以下是测试:

[Subject(typeof(CSVProcessor))]
public class when_processing_an_email_recipient_header
{
    static string recipientHeaderToParse1 = @"""Lastname, Firstname"" <firstname_lastname@domain.com>" + "," +
                                           @"<testto@domain.com>, testto1@domain.com, testto2@domain.com" + "," +
                                           @"<testcc@domain.com>, test3@domain.com" + "," +
                                           @"""""Yes, this is valid""""@[emails are hard to parse!]" + "," +
                                           @"First, Last <name@domain.com>, name@domain.com, First Last <name@domain.com>"
                                           ;

    static string[] results1;
    static string[] expectedResults1;

    Establish context = () =>
    {
        expectedResults1 = new string[]
        {
            @"Lastname",
            @"Firstname <firstname_lastname@domain.com>",
            @"<testto@domain.com>",
            @"testto1@domain.com",
            @"testto2@domain.com",
            @"<testcc@domain.com>",
            @"test3@domain.com",
            @"Yes",
            @"this is valid@[emails are hard to parse!]",
            @"First",
            @"Last <name@domain.com>",
            @"name@domain.com",
            @"First Last <name@domain.com>"
        };
    };

    Because of = () =>
    {
        results1 = CSVProcessor.GetFieldsFromString(recipientHeaderToParse1);
    };

    It should_parse_the_email_parts_properly = () => results1.ShouldBeLike(expectedResults1);
}

能否提供 SetUpTextFieldParser() 的代码? - TheEdge

-2
我在Java中使用以下正则表达式来从符合RFC标准的电子邮件地址中获取电子邮件字符串:
[A-Za-z0-9]+[A-Za-z0-9._-]+@[A-Za-z0-9]+[A-Za-z0-9._-]+[.][A-Za-z0-9]{2,3}

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