function configuration_fetch in Configuration Management 6
Find parts of an array based on a semi-compatible xpath syntax.
Returns an array of context items that match and reference the desired parts of an array
Loosely based off of Cake function Set::extract
Supports the following xpath syntax examples
- /block
- /menu/id
- /menu/id=3
- /modules/*
- /content/@action
- /content/@action=update
- //*
- //tag
- //@include
- /view//display//id
- /block[module=block]/delta
- /block[module=block][delta=1]
- /block[not(module)][not(delta)]
TODO: Test referenced matches and make sure that changes to one match (say a parent of a matched child) are updated in the other matches as well.
Parameters
$path: The configuration path (xpath) that should lead to the matches
$context: The previously built context object that is used to traverse the data
$return_empty: If TRUE this fetch operation will return empty objects for each place where a potential match would have been
Return value
Return an indexed array of matched context items
7 calls to configuration_fetch()
- configuration_apply_map in ./configuration.module 
- Apply an individual configuration map to the data set object
- configuration_context_process_property in ./configuration.module 
- Process a single mapping config property. Various properties will mark static data that will be processed later on in the configuration phases.
- configuration_load_includes in ./configuration.module 
- Load all include files including the module configuration component files supplied by the configuration framework
- configuration_process_action in ./configuration.module 
- Execute a single action
- configuration_replace_tokens in ./configuration.module 
- Find and replace identifier and token keys and values
File
- ./configuration.module, line 1382 
- Provide a unified method for defining site configurations abstracted from their data format. Various data formats should be supported via a plugin architecture such as XML, YAML, JSON, PHP
Code
function configuration_fetch($path, &$context, $return_empty = false) {
  // TODO Think a bit more about the necessity of $match_record
  static $match_record;
  if ($path === '/') {
    $matches = array(
      &$context,
    );
    $match_record[$path] = $matches;
    return $match_record[$path];
  }
  else {
    if ($path[0] !== '/') {
      $path = '/' . $path;
    }
  }
  // Start the list of contexts
  $list = array(
    &$context,
  );
  // Create a list of tokens based on the supplied path
  $tokens = array_slice(preg_split('/(?<!=)\\/(?![a-z]*\\])/', $path), 1);
  $all = false;
  $all_matches = array();
  $token = null;
  $previous = null;
  $match = null;
  while (!empty($tokens)) {
    $token = array_shift($tokens);
    // Get any look ahead section
    $look_aheads = array();
    if (preg_match_all('/\\[(.*?)\\]/', $token, $m)) {
      foreach ($m[0] as $remove) {
        $token = str_replace($remove, '', $token);
      }
      $look_aheads = $m[1];
    }
    // TODO Implement better conditionals for each token
    // Currently only supports element=value conditions
    $conditions = array();
    if (preg_match('/(=)(.*)/', $token, $m)) {
      $conditions[$m[1]] = $m[2];
      $token = substr($token, 0, strpos($token, $m[1]));
    }
    $matches = array();
    foreach ($list as &$piece) {
      if ($token === '..') {
        $matches[] =& $piece->parent;
        continue;
      }
      else {
        if ($token === '') {
          $matches[] =& $piece;
          continue;
        }
        else {
          if ($previous === '') {
            for ($i = 0; $i < count($piece->children); $i++) {
              $matches[] =& $piece->children[$i];
            }
          }
        }
      }
      if (is_array($piece->item)) {
        $i = 0;
        while (isset($piece->children[$i])) {
          unset($match);
          // Allow matches to match against a context-only secondary_key
          if ($piece->children[$i]->key === $token || $piece->children[$i]->secondary_key === $token) {
            $match =& $piece->children[$i];
          }
          else {
            if ($token === '*') {
              $match =& $piece->children[$i];
            }
          }
          // Select attributes
          if ($token[0] == '@' && isset($piece->children[$i]->{substr($token, 1)})) {
            //  $match = &$piece->children[$i];
          }
          if (isset($match) && $previous === '') {
            $all_matches[] =& $match;
          }
          else {
            if (isset($match)) {
              $matches[] =& $match;
            }
          }
          $i++;
        }
      }
      else {
        if ($token === '.') {
          $matches[] =& $piece;
        }
      }
      // Select current piece if looking for an attribute
      if ($token[0] == '@' && isset($piece->{substr($token, 1)})) {
        if ($previous === '') {
          $all_matches[] =& $piece;
        }
        else {
          $matches[] =& $piece;
        }
      }
    }
    // Filter matches from the matches list based on our conditions
    foreach ($conditions as $operator => $value) {
      _configuration_array_filter($matches, $operator, $value, $token[0] == '@' ? substr($token, 1) : null);
    }
    // Filter matches based on look-ahead checks
    foreach ($look_aheads as $ahead) {
      _configuration_array_look_ahead($matches, $ahead, $token);
    }
    // Update the context area to the next set of matches to dig into
    // First, continue the same token if we are not done selecting all (//)
    if (!empty($matches) && $previous === '') {
      array_unshift($tokens, $token);
      $list = $matches;
      continue;
    }
    else {
      if (empty($matches) && $previous === '') {
        $matches = $all_matches;
        $all_matches = array();
      }
    }
    // If we are at the end and no matches, tag on empty matches
    if ($return_empty && empty($tokens) && empty($matches) && $token[0] != '@') {
      foreach ($list as &$piece) {
        // Do not attach an empty child if the parent is not an array
        if (!is_array($piece->parent->item)) {
          continue;
        }
        $match = new stdClass();
        $match->empty = TRUE;
        $match->item = null;
        $match->key = !in_array($token, array(
          '*',
          '.',
          '..',
        )) ? $token : null;
        $match->trace = configuration_trace_context($piece);
        $match->parent =& $piece;
        $match->children = array();
        $matches[] = $match;
      }
    }
    else {
      if (empty($tokens) && $token[0] == '@') {
        if ($return_empty && empty($matches)) {
          $attribute_list =& $list;
        }
        else {
          $attribute_list =& $matches;
        }
        for ($i = 0; isset($attribute_list[$i]); $i++) {
          $match =& $attribute_list[$i];
          $attribute = new stdClass();
          $attribute->empty = empty($matches) ? true : false;
          $attribute->_attribute = true;
          $attribute->key = substr($token, 1);
          $attribute->item =& $match->{$attribute->key};
          $attribute->trace = $match->trace;
          $attribute->parent =& $match;
          $attribute->children = array();
          unset($matches[$i]);
          $matches[$i] =& $attribute;
        }
      }
      else {
        if (!empty($tokens) && empty($matches)) {
          break;
        }
        else {
          $list = $matches;
        }
      }
    }
    $previous = $token;
  }
  // Make sure the matches stay recorded here so that changes to the
  // context in check_context will update obtained matches objects
  $match_record[$path] = $matches;
  // Return the list of matches
  return $match_record[$path];
}