Повторение группы PHP preg_match

У меня есть регулярное выражение (php5.2 и 5.3), которому нужно извлечь первые $x предложений из сообщения пользователя (которые могут включать адреса электронной почты и гиперссылки), и у меня возникли проблемы с попыткой выяснить, почему (и да, это уродливое регулярное выражение ; Оптимизирую, когда заработает):

/^(([^.!?]+|(\w+[.@?&=%:])+\w+)+[.!?]+\s){0,4}/

возвращает первые четыре предложения, но

/^(([^.!?]+|(\w+[.@?&=%:])+\w+)+[.!?]+\s){0,5}/

не возвращает совпадений. Насколько я понимаю, {0,5} должен соответствовать предыдущей группе от 0 до 5 раз, и поэтому все равно должен работать, если он может соответствовать ей только 4 раза.

Может ли кто-нибудь пролить свет на это поведение?

Обновление: $x — просто произвольное число; используя {0,$x} в регулярном выражении. Сообщение фильтруется, чтобы быть предложениями, разделенными одним пробелом. Извините за уродливое выражение... изучаю это уже пару дней, и это делает мне голову... Сделал изменения, предложенные савой. Мой главный вопрос касается поведения, и содержание, которому соответствует группа, не должно иметь большого значения.

Update2: это, по сути, то, что я делаю:

function extractSummary($message, $limit) {
  $expr = '/^(([^.!?]+|(\w+[.@?&=%:])+\w+)+[.!?]+\s){0,'.$limit.'}/';
  $msg = preg_replace('/[\x00-\x1f\x80-\xff]/', "\n" strip_tags($message));
  $msg = trim(preg_replace('/(\n|\s| )+/', ' ', $msg)).' ';
  preg_match($expr, $msg, $summary);
  return $summary[0];
}

Предложение (по крайней мере, на мой взгляд, не заходя на территорию НЛП, потому что оно предназначено только для одной функции на сайте) — это что угодно вплоть до точки, восклицательного или вопросительного знака, но точки могут появляться в предложении в адресе электронной почты пользователя. URL. Последняя версия этого регулярного выражения просто насчитывала до 5 периодов и, таким образом, ломалась по ссылкам и адресам электронной почты.

Обновление 3: Понимая, что я только что добавил еще более ужасный код, я объясню последнее. Было обнаружено, что некоторый опубликованный контент содержит непечатаемые символы (например, \r и т. д.), которые плохо сочетаются с регулярным выражением, поэтому я удаляю непечатаемые символы с помощью первого preg_replace. Второй заменяет любые дополнительные группы пробелов одним пробелом, поэтому мы надеемся, что предложения разделены ровно одним пробелом.


person Rodney    schedule 29.03.2011    source источник
comment
Что такое $x предложений? Как пост?   -  person sidyll    schedule 29.03.2011
comment
Прежде чем сделать это, вы должны очистить свое регулярное выражение. Например, вам не нужна крайняя пара круглых скобок в ((\w+[....\w), и вы не согласны с захватами: иногда у вас есть (...), а иногда у вас есть (?:...). Используйте первый только тогда, когда вы хотите извлечь эту часть. Я думаю, что невежливо просто публиковать сложное регулярное выражение и позволять людям следовать ему.   -  person sawa    schedule 29.03.2011
comment
Спасибо за предложение, но я думаю, что несколько примеров предложений необходимы, или, по крайней мере, вы должны сказать нам, что вы имеете в виду в качестве предложения. Является ли предложение просто последовательностью до точки? Судя по тому, что у вас есть, это, вероятно, не так. Допускается ли точка в предложении только тогда, когда она является частью адреса электронной почты? Какое условие определяет предложение?   -  person sawa    schedule 29.03.2011


Ответы (3)


Я узнаю фразу следующим образом:

Предложение:

  • кратчайшая последовательность до точки, восклицательного или вопросительного знака,
  • необязательно, за которым следует одинарная или двойная кавычка,
  • обязательно после пробела или конца строки.

Это требование пробела или конца строки касается точек в адресах электронной почты, поскольку точки в адресах электронной почты не появляются непосредственно перед пробелом или в конце строки.

/[^ ](?:.*?[.!?]['"]*(?= |\z)){0,4}/
person sawa    schedule 29.03.2011
comment
Это приводит к отсутствию совпадений даже в базовом случае. Попытка с Это предложение. Это предложение 2. В этом предложении адрес электронной почты.адрес@домен.com. Здесь есть ссылка somewhere.com. Это предложение не должно появляться в выводе. - person Rodney; 29.03.2011
comment
Да, пробовал оба. Обновить ранее, результат не не соответствует, а соответствует пустой строке. Что имеет смысл из регулярного выражения, но это не то, что мне нужно - мне нужно извлекать предложения, а не проверять совпадение. - person Rodney; 29.03.2011
comment
Это извлекает только первое предложение. С небольшим изменением он получает первые 4 предложения: /(.*?[.!?](?=\s)){0,4}/, однако все еще прерывается, когда встречается адрес электронной почты или ссылка. - person Rodney; 29.03.2011
comment
Не обрабатывает предложения, которые заканчиваются кавычками, например. Like "this one". или He said: 'it didn't work!' - person ridgerunner; 29.03.2011
comment
Я ошибся в том, где добавить кавычки. Я исправил это и отредактировал весь ответ. - person sawa; 29.03.2011

Регулярное выражение заканчивается безусловным соответствием символу пробела. Если во входных данных ровно 5 предложений и нет пробелов после последней точки, первое совпадет, а второе — нет.

person Jon    schedule 29.03.2011
comment
Это не объясняет, почему первое регулярное выражение удалось, а второе не удалось, как утверждает Родни. - person sawa; 29.03.2011
comment
Сообщение имеет пробел, вставленный в конце, прежде чем он будет запущен через регулярное выражение, чтобы избежать этой возможности. - person Rodney; 29.03.2011

Эта проверенная функция должна помочь:

function get_sentences($text, $x) {
    $regex = "/\A(?:.*?[\w\"'][.?!](?=['\"]?\s|\$)){0,{$x}}/ms";
    if (preg_match($regex, $text, $matches)) return $matches[0];
    return ''; // Never get here (will always match).
}

Вот закомментированная версия регулярного выражения:

$regex = '/# Match first $x sentences, each ending in [.?!]
    \A                # Anchor to beginning of string
    (?:               # Non-capture group to apply count
      .*?             # Lazily match zero or more characters.
      [\w"\']         # Last char before end is word or quote.
      [.?!]           # End of sentence puntuation [.?!]
      (?=[\'"]?\s|$)  # But only if followed by space or EOL
    ){0,5}            # Match from zero to $x sentences.
    /smx';

Обратите внимание, что это также обрабатывает предложения, которые заканчиваются кавычками, например. "This one." или "This one!" или "Вот этот"?

person ridgerunner    schedule 29.03.2011
comment
Хорошо, но останавливает вывод перед предложением со ссылкой в ​​нем: например, это предложение один. Это два. Это кто-то@somewhere.com три. только выводит Это предложение один. Это два. даже если $x › 2. - person Rodney; 29.03.2011
comment
@Rodney: Нет. Это прекрасно работает с вашим примером. За конечным знаком препинания должен следовать пробел или конец строки. - person ridgerunner; 29.03.2011
comment
Мое плохое, опечатка твоего выражения. Работает хорошо. Огромное спасибо! - person Rodney; 29.03.2011