lithium\util\Set::extract()
Implements partial support for XPath 2.0.
Parameters
-
array
$data
An array of data to extract from.
-
string
$path
An absolute XPath 2.0 path. Only absolute paths starting with a single slash are supported right now. Implemented selectors:
'/User/id'
: Similar to the classic {n}.User.id.'/User[2]/name'
: Selects the name of the second User.'/User[id>2]'
: Selects all Users with an id > 2.'/User[id>2][<5]'
: Selects all Users with an id > 2 but < 5.'/Post/Comment[author_name=John]/../name'
: Selects the name of all posts that have at least one comment written by John.'/Posts[name]'
: Selects all Posts that have a'name'
key.'/Comment/.[1]'
: Selects the contents of the first comment.'/Comment/.[:last]'
: Selects the last comment.'/Comment/.[:first]'
: Selects the first comment.'/Comment[text=/lithium/i]
': Selects the all comments that have a text matching the regex/lithium/i
.'/Comment/@*'
: Selects all key names of all comments.
-
array
$options
Currently only supports
'flatten'
which can be disabled for higher XPath-ness.
Returns
arrayAn array of matched items.
Source
public static function extract(array $data, $path = null, array $options = []) {
$defaults = ['flatten' => true];
$options += $defaults;
if (!$data) {
return [];
}
if ($path === '/') {
return array_filter($data, function($data) {
return ($data === 0 || $data === '0' || !empty($data));
});
}
$contexts = $data;
if (!isset($contexts[0])) {
$contexts = [$data];
}
$tokens = array_slice(preg_split('/(?<!=)\/(?![a-z-]*\])/', $path), 1);
do {
$token = array_shift($tokens);
$conditions = false;
if (preg_match_all('/\[([^=]+=\/[^\/]+\/|[^\]]+)\]/', $token, $m)) {
$conditions = $m[1];
$token = substr($token, 0, strpos($token, '['));
}
$matches = [];
foreach ($contexts as $key => $context) {
if (!isset($context['trace'])) {
$context = ['trace' => [null], 'item' => $context, 'key' => $key];
}
if ($token === '..') {
if (count($context['trace']) === 1) {
$context['trace'][] = $context['key'];
}
array_pop($context['trace']);
$parent = join('/', $context['trace']);
$context['item'] = static::extract($data, $parent);
array_pop($context['trace']);
$context['item'] = array_shift($context['item']);
$matches[] = $context;
continue;
}
$match = false;
if ($token === '@*' && is_array($context['item'])) {
$matches[] = [
'trace' => array_merge($context['trace'], (array) $key),
'key' => $key,
'item' => array_keys($context['item'])
];
} elseif (is_array($context['item']) && isset($context['item'][$token])) {
$items = $context['item'][$token];
if (!is_array($items)) {
$items = [$items];
} elseif (!isset($items[0])) {
$current = current($items);
if ((is_array($current) && count($items) <= 1) || !is_array($current)) {
$items = [$items];
}
}
foreach ($items as $key => $item) {
$ctext = [$context['key']];
if (!is_numeric($key)) {
$ctext[] = $token;
$token = array_shift($tokens);
if (isset($items[$token])) {
$ctext[] = $token;
$item = $items[$token];
$matches[] = [
'trace' => array_merge($context['trace'], $ctext),
'key' => $key,
'item' => $item
];
break;
} else {
array_unshift($tokens, $token);
}
} else {
$ctext[] = $token;
}
$matches[] = [
'trace' => array_merge($context['trace'], $ctext),
'key' => $key,
'item' => $item
];
}
} elseif (
$key === $token || (is_numeric($token) && $key == $token) || $token === '.'
) {
$context['trace'][] = $key;
$matches[] = [
'trace' => $context['trace'],
'key' => $key,
'item' => $context['item']
];
}
}
if ($conditions) {
foreach ($conditions as $condition) {
$filtered = [];
$length = count($matches);
foreach ($matches as $i => $match) {
if (static::matches($match['item'], [$condition], $i + 1, $length)) {
$filtered[] = $match;
}
}
$matches = $filtered;
}
}
$contexts = $matches;
if (empty($tokens)) {
break;
}
} while (true);
$r = [];
foreach ($matches as $match) {
$key = array_pop($match['trace']);
$condition = (!is_int($key) && $key !== null);
if ((!$options['flatten'] || is_array($match['item'])) && $condition) {
$r[] = [$key => $match['item']];
} else {
$r[] = $match['item'];
}
}
return $r;
}