Примечание
Я больше знаком с JavaScript, чем с Python, и основная логика у них одинакова (разница в синтаксисе), поэтому я написал здесь свои решения на JavaScript. Не стесняйтесь переводить на Python.
Проблема
Этот вопрос немного сложнее, чем простой однострочный скрипт или регулярное выражение, но в зависимости от конкретных требований вы можете обойтись чем-то элементарным.
Во-первых, синтаксический анализ электронной почты не сводится к одному регулярному выражению. На этом веб-сайте есть несколько примеров регулярных выражений, которые будут соответствовать "многим" электронным письмам, но объясняет компромиссы (сложность против точности) и продолжает включать стандартное регулярное выражение RFC 5322, которое теоретически должно соответствовать любому электронному письму, за которым следует абзац для почему вы не должны использовать его. Однако даже это регулярное выражение предполагает, что доменное имя, принимающее форму IP-адреса, может состоять только из кортежа из четырех целых чисел в диапазоне от 0 до 255 — это не так. т разрешить IPv6
Даже что-то такое простое, как:
{a, b}@domain.com
Может произойти сбой, поскольку технически, согласно спецификации адреса электронной почты, адрес электронной почты может содержать ЛЮБЫЕ символы ASCII, заключенные в кавычки. Ниже приведен действительный (единственный) адрес электронной почты:
"{a, b}"@domain.com
Чтобы точно разобрать электронное письмо, потребуется, чтобы вы читали символы по одной букве за раз и построили конечный автомат, чтобы отслеживать, находитесь ли вы в двойной кавычке, в фигурной скобке, перед @
, после @
, синтаксический анализ доменное имя, анализ IP-адреса и т. д. Таким образом, вы можете токенизировать адрес, найти токен фигурной скобки и проанализировать его независимо.
Что-то элементарное
Регулярные выражения не обеспечивают стопроцентной точности и поддержки всех электронных писем, *особенно*, если вы хотите поддерживать более одного электронного письма в одной строке. Но мы начнем с них и попробуем построить оттуда.
Вы, вероятно, пробовали регулярное выражение, например:
/\{(([^,]+),?)+\}\@(\w+\.)+[A-Za-z]+/
- Совпадение с одной фигурной скобкой...
- Followed by one or more instances of:
- One or more non-comma characters...
- После нуля или одной запятой
- За ним следует одна закрывающая фигурная скобка...
- За ним следует один
@
- Followed by one or more instances of:
- One or more "word" characters...
- За ним следует один
.
- За которым следует один или несколько буквенных символов
Это должно соответствовать чему-то примерно в форме:
{one, two}@domain1.domain2.toplevel
Это обрабатывает проверку, затем следует извлечение всех допустимых адресов электронной почты. Обратите внимание, что у нас есть два набора скобок в части имени адреса электронной почты, которые являются вложенными: (([^,]+),?)
. Это вызывает у нас проблему. Многие механизмы регулярных выражений не знают, как возвращать совпадения в этом случае. Посмотрите, что происходит, когда я запускаю это в JavaScript, используя консоль разработчика Chrome:
var regex = /\{(([^,]+),?)+\}\@(\w+\.)+[A-Za-z]+/
var matches = "{one, two}@domain.com".match(regex)
Array(4) [ "{one, two}@domain.com", " two", " two", "domain." ]
Что ж, это было неправильно. Он нашел two
дважды, но не нашел one
один раз! Чтобы исправить это, нам нужно устранить вложенность и сделать это в два этапа.
var regexOne = /\{([^}]+)\}\@(\w+\.)+[A-Za-z]+/
"{one, two}@domain.com".match(regexOne)
Array(3) [ "{one, two}@domain.com", "one, two", "domain." ]
Теперь мы можем использовать совпадение и анализировать его отдельно:
// Note: It's important that this be a global regex (the /g modifier) since we expect the pattern to match multiple times
var regexTwo = /([^,]+,?)/g
var nameMatches = matches[1].match(regexTwo)
Array(2) [ "one,", " two" ]
Теперь мы можем обрезать их и получить наши имена:
nameMatches.map(name => name.replace(/, /g, "")
nameMatches
Array(2) [ "one", "two" ]
Для создания «доменовой» части электронного письма нам понадобится аналогичная логика для всего, что следует после @
, поскольку это может повторяться так же, как и часть имени может повторяться. Наш окончательный код (на JavaScript) может выглядеть примерно так (вам придется самостоятельно конвертировать в Python):
function getEmails(input)
{
var emailRegex = /([^@]+)\@(.+)/;
var emailParts = input.match(emailRegex);
var name = emailParts[1];
var domain = emailParts[2];
var nameList;
if (/\{.+\}/.test(name))
{
// The name takes the form "{...}"
var nameRegex = /([^,]+,?)/g;
var nameParts = name.match(nameRegex);
nameList = nameParts.map(name => name.replace(/\{|\}|,| /g, ""));
}
else
{
// The name is not surrounded by curly braces
nameList = [name];
}
return nameList.map(name => `${name}@${domain}`);
}
Несколько линий электронной почты
Здесь все становится сложнее, и нам нужно принять немного меньшую точность, если мы не хотим строить полный лексер / токенизатор. Поскольку наши электронные письма содержат запятые (в поле имени), мы не можем точно разделить запятые, если только эти запятые не заключены в фигурные скобки. С моим знанием регулярных выражений я не знаю, легко ли это сделать. Это может быть возможно с операторами просмотра вперед или назад, но кто-то другой должен будет рассказать мне об этом.
Однако с помощью регулярных выражений можно легко найти блок текста, содержащий запятую после амперсанда. Что-то вроде: @[^@{]+?,
В строке [email protected], [email protected]
это будет соответствовать всей фразе @b.com,
, но важно то, что это дает нам место для разделения строки. Хитрость заключается в том, чтобы узнать, как разделить вашу строку здесь. Что-то вроде этого будет работать большую часть времени:
var emails = "[email protected], [email protected]"
var matches = emails.match(/@[^@{]+?,/g)
var split = emails.split(matches[0])
console.log(split) // Array(2) [ "a", " [email protected]" ]
split[0] = split[0] + matches[0] // Add back in what we split on
У этого есть потенциальная ошибка, если у вас есть два электронных письма в списке с одним и тем же доменом:
var emails = "[email protected], [email protected], [email protected]"
var matches = emails.match(@[^@{]+?,/g)
var split = emails.split(matches[0])
console.log(split) // Array(3) [ "a", " c", " [email protected]" ]
split[0] = split[0] + matches[0]
console.log(split) // Array(3) [ "[email protected]", " c", " [email protected]" ]
Но опять же, не создавая лексер/токенизатор, мы соглашаемся с тем, что наше решение будет работать только в большинстве случаев, а не во всех.
Однако, поскольку задача разделения одной строки на несколько электронных писем проще, чем погружение в электронное письмо, извлечение имени и разбор имени: мы можем написать действительно глупый лексер только для этой части:
var inBrackets = false
var emails = "{a, b}@c.com, [email protected]"
var split = []
var lastSplit = 0
for (var i = 0; i < emails.length; i++)
{
if (inBrackets && emails[i] === "}")
inBrackets = false;
if (!inBrackets && emails[i] === "{")
inBrackets = true;
if (!inBrackets && emails[i] === ",")
{
split.push(emails.substring(lastSplit, i))
lastSplit = i + 1 // Skip the comma
}
}
split.push(emails.substring(lastSplit))
console.log(split)
Опять же, это не будет идеальным решением, поскольку адрес электронной почты может существовать следующим образом:
","@domain.com
Но для 99% случаев использования этого простого лексера будет достаточно, и теперь мы можем создать «обычно работающее, но не идеальное» решение, подобное следующему:
function getEmails(input)
{
var emailRegex = /([^@]+)\@(.+)/;
var emailParts = input.match(emailRegex);
var name = emailParts[1];
var domain = emailParts[2];
var nameList;
if (/\{.+\}/.test(name))
{
// The name takes the form "{...}"
var nameRegex = /([^,]+,?)/g;
var nameParts = name.match(nameRegex);
nameList = nameParts.map(name => name.replace(/\{|\}|,| /g, ""));
}
else
{
// The name is not surrounded by curly braces
nameList = [name];
}
return nameList.map(name => `${name}@${domain}`);
}
function splitLine(line)
{
var inBrackets = false;
var split = [];
var lastSplit = 0;
for (var i = 0; i < line.length; i++)
{
if (inBrackets && line[i] === "}")
inBrackets = false;
if (!inBrackets && line[i] === "{")
inBrackets = true;
if (!inBrackets && line[i] === ",")
{
split.push(line.substring(lastSplit, i));
lastSplit = i + 1;
}
}
split.push(line.substring(lastSplit));
return split;
}
var line = "{a.b, c.d, e.f}@uni.somewhere, {x.y, z.k}@edu.com";
var emails = splitLine(line);
var finalList = [];
for (var i = 0; i < emails.length; i++)
{
finalList = finalList.concat(getEmails(emails[i]));
}
console.log(finalList);
// Outputs: [ "[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]" ]
Если вы хотите попробовать реализовать полное решение для лексера/токенизатора, вы можете посмотреть на простой/тупой лексер, который я создал в качестве отправной точки. Общая идея заключается в том, что у вас есть машина состояний (в моем случае у меня было только два состояния: inBrackets
и !inBrackets
), и вы читаете по одной букве за раз, но интерпретируете ее по-разному в зависимости от вашего текущего состояния.
person
stevendesu
schedule
01.04.2019