Проблемы с PHP preg_match_all

Я написал регулярное выражение, которое протестировал на сайте rubular.com, и оно вернуло 4 совпадения. Тему тестирования можно найти здесь http://pastebin.com/49ERrzJN, а PHP-код приведен ниже. По какой-то причине код PHP возвращает только первые 2 совпадения. Как сделать так, чтобы совпадали все 4? Кажется, это как-то связано с жадностью или около того.

$file = file_get_contents('x.txt');
preg_match_all('~[0-9]+\s+(((?!\d{7,}).){2,20})\s{2,30}(((?!\d{7,}).){2,30})\s+([0-9]+)-([0-9]+)-([0-9]+)\s+(F|M)\s+(.{3,25})\s+(((?!\d{7,}).){2,50})~', $file, $m, PREG_SET_ORDER);
foreach($m as $v) echo 'S: '. $v[1]. '; N: '. $v[3]. '; D:'. $v[7]. '<br>';

person pedmillon    schedule 03.04.2016    source источник
comment
Что именно вы пытаетесь извлечь?   -  person Kaspar Lee    schedule 03.04.2016
comment
Разве rubular.com не для Ruby?   -  person Laurel    schedule 03.04.2016
comment
@Druzion: имя, фамилия, год рождения, пол больше всего   -  person pedmillon    schedule 03.04.2016


Ответы (2)


Ваше регулярное выражение очень медленное. Попробовав это на regex101.com, я обнаружил, что время ожидания на PHP (но не на JS по какой-то причине). Я почти уверен, что тайм-аут происходит примерно через 50 000 шагов. На самом деле, теперь понятно, почему вы не используете онлайн-тестер регулярных выражений PHP.

Я не уверен, является ли это источником вашей проблемы, но есть память по умолчанию лимит в PHP:

memory_limit [по умолчанию:] 128M

[история:] 8M до PHP 5.2.0, 16M в PHP 5.2.0

Если вы используете модификатор multiline (я предполагаю, что preg_match_all по сути добавляет модификатор global), вы можете использовать это регулярное выражение, которое выполняет всего 1282 шага, чтобы найти все 4 совпадения:

^ [0-9]+\s+(((?!\d{7,}).){2,20})\s{2,30}(((?!\d{7,}).){2,30})\s+([0-9]+)-([0-9]+)-([0-9]+)\s+(F|M)\s+(.{3,25})\s+(((?!\d{7,}).){2,50})

На самом деле, я добавил только 2 персонажа. Они в начале, якорь ^ и буквальное пространство.

person Laurel    schedule 03.04.2016
comment
спасибо, добавление 2 символов и использование модификатора m сделали свою работу - person pedmillon; 03.04.2016
comment
Проблемы с шаблонами не имеют ничего общего с ограничением памяти PHP. - person Casimir et Hippolyte; 03.04.2016

Если вам нужно написать длинный шаблон, первое, что нужно сделать, это сделать его читабельным. Для этого используйте подробный режим (модификатор x), который разрешает комментарии и пробелы, а также используйте именованные захваты.

Затем вам нужно сделать точное описание того, что вы ищете:

  • ваша цель занимает целую строку => используйте привязки ^ и $ с модификатором m и используйте класс \h (который содержит только горизонтальные пробелы) вместо класса \s.
  • вместо того, чтобы использовать такого рода неэффективные подшаблоны (?:(?!.....).){m,n} для описания того, что ваше поле не должно содержать, опишите, что поле может содержать.
  • используйте атомарные группы (?>...), когда это необходимо, вместо групп без захвата, чтобы избежать бесполезного возврата.
  • в общем, использование точных классов символов позволяет избежать многих проблем

шаблон:

~
^ \h*+ # start of the line
# named captures                            # field separators
(?<VOTERNO>     [0-9]+                     )  \h+
(?<SURNAME>     \S+ (?>\h\S+)*?            )  \h{2,}
(?<OTHERNAMES>  \S+ (?>\h\S+)*?            )  \h{2,}
(?<DOB>         [0-9]{2}-[0-9]{2}-[0-9]{4} )  \h+
(?<SEX>         [FM]                       )  \h+
(?<APPID_RECNO> [0-9A-Z/]+                 )  \h+
(?<VILLAGE>     \S+ (?>\h\S+)*             ) 
\h* $  # end of the line
~mx

демонстрация

Если вы хотите узнать, что не так с шаблоном, вы можете использовать функцию preg_last_error()

person Casimir et Hippolyte    schedule 03.04.2016