lithium\util\Set::extract()

public static method

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

array

An array of matched items.

Source

	public static function extract(array $data, $path = null, array $options = array()) {
		$defaults = array('flatten' => true);
		$options += $defaults;

		if (!$data) {
			return array();
		}
		if ($path === '/') {
			return array_filter($data, function($data) {
				return ($data === 0 || $data === '0' || !empty($data));
			});
		}
		$contexts = $data;

		if (!isset($contexts[0])) {
			$contexts = array($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 = array();

			foreach ($contexts as $key => $context) {
				if (!isset($context['trace'])) {
					$context = array('trace' => array(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[] = array(
						'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 = array($items);
					} elseif (!isset($items[0])) {
						$current = current($items);
						if ((is_array($current) && count($items) <= 1) || !is_array($current)) {
							$items = array($items);
						}
					}

					foreach ($items as $key => $item) {
						$ctext = array($context['key']);
						if (!is_numeric($key)) {
							$ctext[] = $token;
							$token = array_shift($tokens);
							if (isset($items[$token])) {
								$ctext[] = $token;
								$item = $items[$token];
								$matches[] = array(
									'trace' => array_merge($context['trace'], $ctext),
									'key' => $key,
									'item' => $item
								);
								break;
							} else {
								array_unshift($tokens, $token);
							}
						} else {
							$ctext[] = $token;
						}

						$matches[] = array(
							'trace' => array_merge($context['trace'], $ctext),
							'key' => $key,
							'item' => $item
						);
					}
				} elseif (
					$key === $token || (is_numeric($token) && $key == $token) || $token === '.'
				) {
					$context['trace'][] = $key;
					$matches[] = array(
						'trace' => $context['trace'],
						'key' => $key,
						'item' => $context['item']
					);
				}
			}
			if ($conditions) {
				foreach ($conditions as $condition) {
					$filtered = array();
					$length = count($matches);

					foreach ($matches as $i => $match) {
						if (static::matches($match['item'], array($condition), $i + 1, $length)) {
							$filtered[] = $match;
						}
					}
					$matches = $filtered;
				}
			}
			$contexts = $matches;

			if (empty($tokens)) {
				break;
			}
		} while (true);

		$r = array();

		foreach ($matches as $match) {
			$key = array_pop($match['trace']);
			$condition = (!is_int($key) && $key !== null);
			if ((!$options['flatten'] || is_array($match['item'])) && $condition) {
				$r[] = array($key => $match['item']);
			} else {
				$r[] = $match['item'];
			}
		}
		return $r;
	}