As @nickb already commented, a repeated capturing group only retains its last match. AFAIK, only .NET provides an implementation that retains all matches. So, I agree with @m.buettner that you must use at least two matches. And @IngmardeLange's solution appears to be an alternate implementation, though I haven't checked it, but still uses at least two matches.
For fun, I devised a way to do this using a single match. The initial idea was to use lookbehinds for the @{article
part, but variable-length lookbehinds aren't supported. Then, (unfortunately, as you're about to witness) I remembered @TimPietzcker once mentioning a trick for implementing variable-length lookbehinds: doing variable-length lookaheads on the reversed string. (Please don't ever actually do use this method.)
<?php
function get_attr_val_matches($tag, $subject)
{
$regex = '/"(\w+)"=(\w+)\s+(?=(?:"\w+"=\w+\s+)*' . strrev($tag) . '\{@)/';
preg_match_all($regex, strrev($subject), $matches, PREG_SET_ORDER);
foreach ($matches as &$match)
{
$match = array_map(strrev, $match);
$match = array($match[0], array_reverse(array_slice($match, 1)));
}
return array_reverse($matches);
}
$tag = 'articles';
$subject = '@{articles mode="extrait" nb="3"}';
print_r(get_attr_val_matches($tag, $subject));
?>
Output:
Array
(
[0] => Array
(
[0] => mode="extrait"
[1] => Array
(
[0] => mode
[1] => extrait
)
)
[1] => Array
(
[0] => nb="3"
[1] => Array
(
[0] => nb
[1] => 3
)
)
)
Here's a running example.
Quite obviously, if I haven't disclaimed this enough already, all the reversing costs more than just doing two matches. But maybe there's an application to generically converting expressions with variable-length lookbehinds, to reversed lookaheads as above, then back. Though probably not.