Как захватить каждый матч повторяющегося шаблона?

У меня есть следующее регулярное выражение:

/xxx ([a-z]+)(?:, ([a-z]+))* xxx/

Я хочу захватить все цвета в следующей тестовой строке:

xxx red, blue, pink, purple xxx

(теперь только красный и фиолетовый попадают в плен)

откройте этот URL и просмотрите соответствующие группы: http://www.regex101.com/r/oZ2cH4

Я прочитал следующее http://www.regular-expressions.info/captureall.html но трюк не сработал

(или возможно я сделал это неправильно)

как я могу решить это?

заранее спасибо

2 ответа

Решение

Возможно, вы захотите вернуть подходящую группу на основе совпадений предыдущего шаблона:

$word = '[a-z]+';
$sep  = '[, ]+';

$words = $captures("~($word)(?:{$sep})?~");
$of    = $captures("~xxx ({$word}(?:{$sep}{$word})*) xxx~");

print_r($words($of($subject)));

Выход:

Array
(
    [0] => red
    [1] => blue
    [2] => pink
    [3] => purple
)

В то время как $captures это функция, которая возвращает предварительно настроенный preg_match_all вызов, позволяющий обрабатывать не только строку как тему, но и все остальное foreach может работать на:

$captures = function ($pattern, $group = 1) {
    return function ($subject) use ($pattern, $group) {
        if (is_string($subject)) {
            $subject = (array)$subject;
        }
        $captures = [];
        foreach ($subject as $step) {
            preg_match_all($pattern, $step, $matches);
            $captures = array_merge($captures, $matches[$group]);
        }
        return $captures;
    };
};

По умолчанию, как показано в примере выше, возвращается первая группа (1), но это можно настроить.

Это позволяет сначала сопоставить внешний шаблон ($of), а затем на каждом из них совпадает внутренний шаблон ($words). Пример полностью:

$subject = '/xxx red, blue, pink, purple xxx/';

$captures = function ($pattern, $group = 1) {
    return function ($subject) use ($pattern, $group) {
        if (is_string($subject)) {
            $subject = (array)$subject;
        }
        $captures = [];
        foreach ($subject as $step) {
            preg_match_all($pattern, $step, $matches);
            $captures = array_merge($captures, $matches[$group]);
        }
        return $captures;
    };
};

$word = '[a-z]+';
$sep  = '[, ]+';
$seq  = "";

$words = $captures("~($word)(?:{$sep})?~");
$of    = $captures("~xxx ({$word}(?:{$sep}{$word})*) xxx~");

print_r($words($of($subject)));

Смотрите live-демо.

В учебнике "Повторение захвата группы против захвата повторяющейся группы" (by регулярно-expressions.info) описывается, как можно захватить весь контент "красный, синий, розовый, фиолетовый" в одном захвате. Шаблон, который он предложил бы,

/xxx ((?:[a-z]+(?:, )?)+) xxx/

но если бы это было действительно то, чего вы пытались достичь, вы также можете использовать более простое выражение

/xxx ([a-z, ]*) xxx/

Я подозреваю, что вы действительно хотите, чтобы захватить каждый цвет в отдельности. Это может быть лучше всего достигнуто путем захвата всего списка один раз, а затем синтаксического анализа захваченного контента.

Другие вопросы по тегам