Извлечение (нескольких) электронных писем из текста, введенного пользователем, в формат MailAddress (.NET)
Класс MailAddress не предоставляет способ разбора строки с несколькими электронными письмами. Класс MailAddressCollection делает, но он принимает только CSV и не допускает запятых внутри кавычек. Я ищу текстовый процессор для создания коллекции электронных писем из пользовательского ввода без этих ограничений.
Процессор должен принимать значения через запятую или точку с запятой в любом из следующих форматов:
"First Middle Last" <fml@example.com>
First Middle Last <fml@example.com>
fml@example.com
"Last, First" <fml@example.com>
4 ответа
Задав связанный вопрос, я узнал о лучшем методе:
/// <summary>
/// Extracts email addresses in the following formats:
/// "Tom W. Smith" <tsmith@contoso.com>
/// "Smith, Tom" <tsmith@contoso.com>
/// Tom W. Smith <tsmith@contoso.com>
/// tsmith@contoso.com
/// Multiple emails can be separated by a comma or semicolon.
/// Watch out for <see cref="FormatException"/>s when enumerating.
/// </summary>
/// <param name="value">Collection of emails in the accepted formats.</param>
/// <returns>
/// A collection of <see cref="System.Net.Mail.MailAddress"/>es.
/// </returns>
/// <exception cref="ArgumentException">Thrown if the value is null, empty, or just whitespace.</exception>
public static IEnumerable<MailAddress> ExtractEmailAddresses(this string value)
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("The arg cannot be null, empty, or just whitespace.", "value");
// Remove commas inside of quotes
value = value.Replace(';', ',');
var emails = value.SplitWhilePreservingQuotedValues(',');
var mailAddresses = emails.Select(email => new MailAddress(email));
return mailAddresses;
}
/// <summary>
/// Splits the string while preserving quoted values (i.e. instances of the delimiter character inside of quotes will not be split apart).
/// Trims leading and trailing whitespace from the individual string values.
/// Does not include empty values.
/// </summary>
/// <param name="value">The string to be split.</param>
/// <param name="delimiter">The delimiter to use to split the string, e.g. ',' for CSV.</param>
/// <returns>A collection of individual strings parsed from the original value.</returns>
public static IEnumerable<string> SplitWhilePreservingQuotedValues(this string value, char delimiter)
{
Regex csvPreservingQuotedStrings = new Regex(string.Format("(\"[^\"]*\"|[^{0}])+", delimiter));
var values =
csvPreservingQuotedStrings.Matches(value)
.Cast<Match>()
.Select(m => m.Value.Trim())
.Where(v => !string.IsNullOrWhiteSpace(v));
return values;
}
Этот метод проходит следующие тесты:
[TestMethod]
public void ExtractEmails_SingleEmail_Matches()
{
string value = "a@a.a";
var expected = new List<MailAddress>
{
new MailAddress("a@a.a"),
};
var actual = value.ExtractEmailAddresses();
CollectionAssert.AreEqual(expected, actual.ToList());
}
[TestMethod()]
public void ExtractEmails_JustEmailCSV_Matches()
{
string value = "a@a.a; a@a.a";
var expected = new List<MailAddress>
{
new MailAddress("a@a.a"),
new MailAddress("a@a.a"),
};
var actual = value.ExtractEmailAddresses();
CollectionAssert.AreEqual(expected, actual.ToList());
}
[TestMethod]
public void ExtractEmails_MultipleWordNameThenEmailSemicolonSV_Matches()
{
string value = "a a a <a@a.a>; a a a <a@a.a>";
var expected = new List<MailAddress>
{
new MailAddress("a a a <a@a.a>"),
new MailAddress("a a a <a@a.a>"),
};
var actual = value.ExtractEmailAddresses();
CollectionAssert.AreEqual(expected, actual.ToList());
}
[TestMethod]
public void ExtractEmails_JustEmailsSemicolonSV_Matches()
{
string value = "a@a.a; a@a.a";
var expected = new List<MailAddress>
{
new MailAddress("a@a.a"),
new MailAddress("a@a.a"),
};
var actual = value.ExtractEmailAddresses();
CollectionAssert.AreEqual(expected, actual.ToList());
}
[TestMethod]
public void ExtractEmails_NameInQuotesWithCommaThenEmailsCSV_Matches()
{
string value = "\"a, a\" <a@a.a>; \"a, a\" <a@a.a>";
var expected = new List<MailAddress>
{
new MailAddress("\"a, a\" <a@a.a>"),
new MailAddress("\"a, a\" <a@a.a>"),
};
var actual = value.ExtractEmailAddresses();
CollectionAssert.AreEqual(expected, actual.ToList());
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ExtractEmails_EmptyString_Throws()
{
string value = string.Empty;
var actual = value.ExtractEmailAddresses();
}
[TestMethod]
[ExpectedException(typeof(FormatException))]
public void ExtractEmails_NonEmailValue_ThrowsOnEnumeration()
{
string value = "a";
var actual = value.ExtractEmailAddresses();
actual.ToList();
}
Подпрограмма MailAddressCollection.Add() поддерживает список адресов, разделенных запятыми.
Dim mc As New Net.Mail.MailAddressCollection()
mc.Add("Bob <bob@bobmail.com>, mary@marymail.com, ""John Doe"" <john.doe@myemail.com>")
For Each m As Net.Mail.MailAddress In mc
Debug.Print("{0} ({1})", m.DisplayName, m.Address)
Next
Выход:
Bob (bob@bobmail.com)
(mary@marymail.com)
John Doe (john.doe@myemail.com)
Библиотека с открытым исходным кодом DotNetOpenMail (old) имеет класс EmailAddress, который может анализировать почти все допустимые формы адресов электронной почты, и EmailAddressCollection. Вы могли бы начать там.
На самом деле MailAddressCollection поддерживает адреса, разделенные запятыми, даже с запятыми внутри кавычек. Реальная проблема, которую я недавно обнаружил, состоит в том, что список CSV уже должен быть закодирован в набор символов ASCII, т.е. Q-кодированный или B-кодированный для адресов Unicode.
В библиотеках базовых классов нет функции для выполнения этой кодировки, хотя я предоставляю B-кодирование в Sasa. Я также только что добавил функцию разбора электронной почты, которая решает вопрос в этой теме.